TypeScript/src/executeCommandLine/executeCommandLine.ts
Alex Munoz 95ef2a503d
feat: display the tsconfig generated when running tsc --init (#45930)
* feat: display the tsconfig generated when running tsc --init

* fix: fix lint issues

* refactor: minor changes
2021-10-01 14:08:06 +01:00

1048 lines
47 KiB
TypeScript

namespace ts {
interface Statistic {
name: string;
value: string;
}
function countLines(program: Program): Map<number> {
const counts = getCountsMap();
forEach(program.getSourceFiles(), file => {
const key = getCountKey(program, file);
const lineCount = getLineStarts(file).length;
counts.set(key, counts.get(key)! + lineCount);
});
return counts;
}
function countNodes(program: Program): Map<number> {
const counts = getCountsMap();
forEach(program.getSourceFiles(), file => {
const key = getCountKey(program, file);
counts.set(key, counts.get(key)! + file.nodeCount);
});
return counts;
}
function getCountsMap() {
const counts = new Map<string, number>();
counts.set("Library", 0);
counts.set("Definitions", 0);
counts.set("TypeScript", 0);
counts.set("JavaScript", 0);
counts.set("JSON", 0);
counts.set("Other", 0);
return counts;
}
function getCountKey(program: Program, file: SourceFile) {
if (program.isSourceFileDefaultLibrary(file)) {
return "Library";
}
else if (file.isDeclarationFile) {
return "Definitions";
}
const path = file.path;
if (fileExtensionIsOneOf(path, supportedTSExtensionsFlat)) {
return "TypeScript";
}
else if (fileExtensionIsOneOf(path, supportedJSExtensionsFlat)) {
return "JavaScript";
}
else if (fileExtensionIs(path, Extension.Json)) {
return "JSON";
}
else {
return "Other";
}
}
function updateReportDiagnostic(
sys: System,
existing: DiagnosticReporter,
options: CompilerOptions | BuildOptions
): DiagnosticReporter {
return shouldBePretty(sys, options) ?
createDiagnosticReporter(sys, /*pretty*/ true) :
existing;
}
function defaultIsPretty(sys: System) {
return !!sys.writeOutputIsTTY && sys.writeOutputIsTTY() && !sys.getEnvironmentVariable("NO_COLOR");
}
function shouldBePretty(sys: System, options: CompilerOptions | BuildOptions) {
if (!options || typeof options.pretty === "undefined") {
return defaultIsPretty(sys);
}
return options.pretty;
}
function getOptionsForHelp(commandLine: ParsedCommandLine) {
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
return !!commandLine.options.all ?
sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) :
filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView);
}
function printVersion(sys: System) {
sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine);
}
function createColors(sys: System) {
const showColors = defaultIsPretty(sys);
if (!showColors) {
return {
bold: (str: string) => str,
blue: (str: string) => str,
blueBackground: (str: string) => str,
brightWhite: (str: string) => str
};
}
function bold(str: string) {
return `\x1b[1m${str}\x1b[22m`;
}
const isWindows = sys.getEnvironmentVariable("OS") && stringContains(sys.getEnvironmentVariable("OS").toLowerCase(), "windows");
const isWindowsTerminal = sys.getEnvironmentVariable("WT_SESSION");
const isVSCode = sys.getEnvironmentVariable("TERM_PROGRAM") && sys.getEnvironmentVariable("TERM_PROGRAM") === "vscode";
function blue(str: string) {
// Effectively Powershell and Command prompt users use cyan instead
// of blue because the default theme doesn't show blue with enough contrast.
if (isWindows && !isWindowsTerminal && !isVSCode) {
return brightWhite(str);
}
return `\x1b[94m${str}\x1b[39m`;
}
// There are ~3 types of terminal color support: 16 colors, 256 and 16m colors
// If there is richer color support, e.g. 256+ we can use extended ANSI codes which are not just generic 'blue'
// but a 'lighter blue' which is closer to the blue in the TS logo.
const supportsRicherColors = sys.getEnvironmentVariable("COLORTERM") === "truecolor" || sys.getEnvironmentVariable("TERM") === "xterm-256color";
function blueBackground(str: string) {
if (supportsRicherColors) {
return `\x1B[48;5;68m${str}\x1B[39;49m`;
}
else {
return `\x1b[44m${str}\x1B[39;49m`;
}
}
function brightWhite(str: string) {
return `\x1b[97m${str}\x1b[39m`;
}
return {
bold,
blue,
brightWhite,
blueBackground
};
}
function getDisplayNameTextOfOption(option: CommandLineOption) {
return `--${option.name}${option.shortName ? `, -${option.shortName}` : ""}`;
}
function generateOptionOutput(sys: System, option: CommandLineOption, rightAlignOfLeft: number, leftAlignOfRight: number) {
interface ValueCandidate {
// "one or more" or "any of"
valueType: string;
possibleValues: string;
}
const text: string[] = [];
const colors = createColors(sys);
// name and description
const name = getDisplayNameTextOfOption(option);
// value type and possible value
const valueCandidates = getValueCandidate(option);
const defaultValueDescription = typeof option.defaultValueDescription === "object" ? getDiagnosticText(option.defaultValueDescription) : option.defaultValueDescription;
const terminalWidth = sys.getWidthOfTerminal?.() ?? 0;
// Note: child_process might return `terminalWidth` as undefined.
if (terminalWidth >= 80) {
let description = "";
if (option.description) {
description = getDiagnosticText(option.description);
}
text.push(...getPrettyOutput(name, description, rightAlignOfLeft, leftAlignOfRight, terminalWidth, /*colorLeft*/ true), sys.newLine);
if (showAdditionalInfoOutput(valueCandidates, option)) {
if (valueCandidates) {
text.push(...getPrettyOutput(valueCandidates.valueType, valueCandidates.possibleValues, rightAlignOfLeft, leftAlignOfRight, terminalWidth, /*colorLeft*/ false), sys.newLine);
}
if (defaultValueDescription) {
text.push(...getPrettyOutput(getDiagnosticText(Diagnostics.default_Colon), defaultValueDescription, rightAlignOfLeft, leftAlignOfRight, terminalWidth, /*colorLeft*/ false), sys.newLine);
}
}
text.push(sys.newLine);
}
else {
text.push(colors.blue(name), sys.newLine);
if (option.description) {
const description = getDiagnosticText(option.description);
text.push(description);
}
text.push(sys.newLine);
if (showAdditionalInfoOutput(valueCandidates, option)) {
if (valueCandidates) {
text.push(`${valueCandidates.valueType} ${valueCandidates.possibleValues}`);
}
if (defaultValueDescription) {
if (valueCandidates) text.push(sys.newLine);
const diagType = getDiagnosticText(Diagnostics.default_Colon);
text.push(`${diagType} ${defaultValueDescription}`);
}
text.push(sys.newLine);
}
text.push(sys.newLine);
}
return text;
function showAdditionalInfoOutput(valueCandidates: ValueCandidate | undefined, option: CommandLineOption): boolean {
const ignoreValues = ["string"];
const ignoredDescriptions = [undefined, "false", "n/a"];
const defaultValueDescription = option.defaultValueDescription;
if (option.category === Diagnostics.Command_line_Options) return false;
if (contains(ignoreValues, valueCandidates?.possibleValues) && contains(ignoredDescriptions, defaultValueDescription)) {
return false;
}
return true;
}
function getPrettyOutput(left: string, right: string, rightAlignOfLeft: number, leftAlignOfRight: number, terminalWidth: number, colorLeft: boolean) {
const res = [];
let isFirstLine = true;
let remainRight = right;
const rightCharacterNumber = terminalWidth - leftAlignOfRight;
while (remainRight.length > 0) {
let curLeft = "";
if (isFirstLine) {
curLeft = padLeft(left, rightAlignOfLeft);
curLeft = padRight(curLeft, leftAlignOfRight);
curLeft = colorLeft ? colors.blue(curLeft) : curLeft;
}
else {
curLeft = padLeft("", leftAlignOfRight);
}
const curRight = remainRight.substr(0, rightCharacterNumber);
remainRight = remainRight.slice(rightCharacterNumber);
res.push(`${curLeft}${curRight}`);
isFirstLine = false;
}
return res;
}
function getValueCandidate(option: CommandLineOption): ValueCandidate | undefined {
// option.type might be "string" | "number" | "boolean" | "object" | "list" | ESMap<string, number | string>
// string -- any of: string
// number -- any of: number
// boolean -- any of: boolean
// object -- null
// list -- one or more: , content depends on `option.element.type`, the same as others
// ESMap<string, number | string> -- any of: key1, key2, ....
if (option.type === "object") {
return undefined;
}
return {
valueType: getValueType(option),
possibleValues: getPossibleValues(option)
};
function getValueType(option: CommandLineOption) {
switch (option.type) {
case "string":
case "number":
case "boolean":
return getDiagnosticText(Diagnostics.type_Colon);
case "list":
return getDiagnosticText(Diagnostics.one_or_more_Colon);
default:
return getDiagnosticText(Diagnostics.one_of_Colon);
}
}
function getPossibleValues(option: CommandLineOption) {
let possibleValues: string;
switch (option.type) {
case "string":
case "number":
case "boolean":
possibleValues = option.type;
break;
case "list":
// TODO: check infinite loop
possibleValues = getPossibleValues(option.element);
break;
case "object":
possibleValues = "";
break;
default:
// ESMap<string, number | string>
const keys = arrayFrom(option.type.keys());
possibleValues = keys.join(", ");
}
return possibleValues;
}
}
}
function generateGroupOptionOutput(sys: System, optionsList: readonly CommandLineOption[]) {
let maxLength = 0;
for (const option of optionsList) {
const curLength = getDisplayNameTextOfOption(option).length;
maxLength = maxLength > curLength ? maxLength : curLength;
}
// left part should be right align, right part should be left align
// assume 2 space between left margin and left part.
const rightAlignOfLeftPart = maxLength + 2;
// assume 2 space between left and right part
const leftAlignOfRightPart = rightAlignOfLeftPart + 2;
let lines: string[] = [];
for (const option of optionsList) {
const tmp = generateOptionOutput(sys, option, rightAlignOfLeftPart, leftAlignOfRightPart);
lines = [...lines, ...tmp];
}
// make sure always a blank line in the end.
if (lines[lines.length - 2] !== sys.newLine) {
lines.push(sys.newLine);
}
return lines;
}
function generateSectionOptionsOutput(sys: System, sectionName: string, options: readonly CommandLineOption[], subCategory: boolean, beforeOptionsDescription?: string, afterOptionsDescription?: string) {
let res: string[] = [];
res.push(createColors(sys).bold(sectionName) + sys.newLine + sys.newLine);
if (beforeOptionsDescription) {
res.push(beforeOptionsDescription + sys.newLine + sys.newLine);
}
if (!subCategory) {
res = [...res, ...generateGroupOptionOutput(sys, options)];
if (afterOptionsDescription) {
res.push(afterOptionsDescription + sys.newLine + sys.newLine);
}
return res;
}
const categoryMap = new Map<string, CommandLineOption[]>();
for (const option of options) {
if (!option.category) {
continue;
}
const curCategory = getDiagnosticText(option.category);
const optionsOfCurCategory = categoryMap.get(curCategory) ?? [];
optionsOfCurCategory.push(option);
categoryMap.set(curCategory, optionsOfCurCategory);
}
categoryMap.forEach((value, key) => {
res.push(`### ${key}${sys.newLine}${sys.newLine}`);
res = [...res, ...generateGroupOptionOutput(sys, value)];
});
if (afterOptionsDescription) {
res.push(afterOptionsDescription + sys.newLine + sys.newLine);
}
return res;
}
function printEasyHelp(sys: System, simpleOptions: readonly CommandLineOption[]) {
const colors = createColors(sys);
let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)];
output.push(colors.bold(getDiagnosticText(Diagnostics.COMMON_COMMANDS)) + sys.newLine + sys.newLine);
example("tsc", Diagnostics.Compiles_the_current_project_tsconfig_json_in_the_working_directory);
example("tsc app.ts util.ts", Diagnostics.Ignoring_tsconfig_json_compiles_the_specified_files_with_default_compiler_options);
example("tsc -b", Diagnostics.Build_a_composite_project_in_the_working_directory);
example("tsc --init", Diagnostics.Creates_a_tsconfig_json_with_the_recommended_settings_in_the_working_directory);
example("tsc -p ./path/to/tsconfig.json", Diagnostics.Compiles_the_TypeScript_project_located_at_the_specified_path);
example("tsc --help --all", Diagnostics.An_expanded_version_of_this_information_showing_all_possible_compiler_options);
example(["tsc --noEmit", "tsc --target esnext"], Diagnostics.Compiles_the_current_project_with_additional_settings);
const cliCommands = simpleOptions.filter(opt => opt.isCommandLineOnly || opt.category === Diagnostics.Command_line_Options);
const configOpts = simpleOptions.filter(opt => !contains(cliCommands, opt));
output = [
...output,
...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.COMMAND_LINE_FLAGS), cliCommands, /*subCategory*/ false, /* beforeOptionsDescription */ undefined, /* afterOptionsDescription*/ undefined),
...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.COMMON_COMPILER_OPTIONS), configOpts, /*subCategory*/ false, /* beforeOptionsDescription */ undefined, formatMessage(/*_dummy*/ undefined, Diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0, "https://aka.ms/tsconfig-reference"))
];
for (const line of output) {
sys.write(line);
}
function example(ex: string | string[], desc: DiagnosticMessage) {
const examples = typeof ex === "string" ? [ex] : ex;
for (const example of examples) {
output.push(" " + colors.blue(example) + sys.newLine);
}
output.push(" " + getDiagnosticText(desc) + sys.newLine + sys.newLine);
}
}
function printAllHelp(sys: System, compilerOptions: readonly CommandLineOption[], buildOptions: readonly CommandLineOption[], watchOptions: readonly CommandLineOption[]) {
let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)];
output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.ALL_COMPILER_OPTIONS), compilerOptions, /*subCategory*/ true, /* beforeOptionsDescription */ undefined, formatMessage(/*_dummy*/ undefined, Diagnostics.You_can_learn_about_all_of_the_compiler_options_at_0, "https://aka.ms/tsconfig-reference"))];
output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.WATCH_OPTIONS), watchOptions, /*subCategory*/ false, getDiagnosticText(Diagnostics.Including_watch_w_will_start_watching_the_current_project_for_the_file_changes_Once_set_you_can_config_watch_mode_with_Colon))];
output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.BUILD_OPTIONS), buildOptions, /*subCategory*/ false, formatMessage(/*_dummy*/ undefined, Diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0, "https://aka.ms/tsc-composite-builds"))];
for (const line of output) {
sys.write(line);
}
}
function printBuildHelp(sys: System, buildOptions: readonly CommandLineOption[]) {
let output: string[] = [...getHeader(sys,`${getDiagnosticText(Diagnostics.tsc_Colon_The_TypeScript_Compiler)} - ${getDiagnosticText(Diagnostics.Version_0, version)}`)];
output = [...output, ...generateSectionOptionsOutput(sys, getDiagnosticText(Diagnostics.BUILD_OPTIONS), buildOptions, /*subCategory*/ false, formatMessage(/*_dummy*/ undefined, Diagnostics.Using_build_b_will_make_tsc_behave_more_like_a_build_orchestrator_than_a_compiler_This_is_used_to_trigger_building_composite_projects_which_you_can_learn_more_about_at_0, "https://aka.ms/tsc-composite-builds"))];
for (const line of output) {
sys.write(line);
}
}
function getHeader(sys: System, message: string) {
const colors = createColors(sys);
const header: string[] = [];
const terminalWidth = sys.getWidthOfTerminal?.() ?? 0;;
const tsIconLength = 5;
const tsIconFirstLine = colors.blueBackground(padLeft("", tsIconLength));
const tsIconSecondLine = colors.blueBackground(colors.brightWhite(padLeft("TS ", tsIconLength)));
// If we have enough space, print TS icon.
if (terminalWidth >= message.length + tsIconLength) {
// right align of the icon is 120 at most.
const rightAlign = terminalWidth > 120 ? 120 : terminalWidth;
const leftAlign = rightAlign - tsIconLength;
header.push(padRight(message, leftAlign) + tsIconFirstLine + sys.newLine);
header.push(padLeft("", leftAlign) + tsIconSecondLine + sys.newLine);
}
else {
header.push(message + sys.newLine);
header.push(sys.newLine);
}
return header;
}
function printHelp(sys: System, commandLine: ParsedCommandLine) {
if (!commandLine.options.all) {
printEasyHelp(sys, getOptionsForHelp(commandLine));
}
else {
printAllHelp(sys, getOptionsForHelp(commandLine), optionsForBuild, optionsForWatch);
}
}
function executeCommandLineWorker(
sys: System,
cb: ExecuteCommandLineCallbacks,
commandLine: ParsedCommandLine,
) {
let reportDiagnostic = createDiagnosticReporter(sys);
if (commandLine.options.build) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_build_must_be_the_first_command_line_argument));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
// Configuration file name (if any)
let configFileName: string | undefined;
if (commandLine.options.locale) {
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
}
// If there are any errors due to command line parsing and/or
// setting up localization, report them and quit.
if (commandLine.errors.length > 0) {
commandLine.errors.forEach(reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (commandLine.options.init) {
writeConfigFile(sys, reportDiagnostic, commandLine.options, commandLine.fileNames);
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.version) {
printVersion(sys);
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.help || commandLine.options.all) {
printHelp(sys, commandLine);
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.watch && commandLine.options.listFilesOnly) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "listFilesOnly"));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (commandLine.options.project) {
if (commandLine.fileNames.length !== 0) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
const fileOrDirectory = normalizePath(commandLine.options.project);
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
else {
configFileName = fileOrDirectory;
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
}
else if (commandLine.fileNames.length === 0) {
const searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath, fileName => sys.fileExists(fileName));
}
if (commandLine.fileNames.length === 0 && !configFileName) {
if (commandLine.options.showConfig) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_current_directory_Colon_0, normalizePath(sys.getCurrentDirectory())));
}
else {
printVersion(sys);
printHelp(sys, commandLine);
}
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
const currentDirectory = sys.getCurrentDirectory();
const commandLineOptions = convertToOptionsWithAbsolutePaths(
commandLine.options,
fileName => getNormalizedAbsolutePath(fileName, currentDirectory)
);
if (configFileName) {
const extendedConfigCache = new Map<string, ExtendedConfigCacheEntry>();
const configParseResult = parseConfigFileWithSystem(configFileName, commandLineOptions, extendedConfigCache, commandLine.watchOptions, sys, reportDiagnostic)!; // TODO: GH#18217
if (commandLineOptions.showConfig) {
if (configParseResult.errors.length !== 0) {
reportDiagnostic = updateReportDiagnostic(
sys,
reportDiagnostic,
configParseResult.options
);
configParseResult.errors.forEach(reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
// eslint-disable-next-line no-null/no-null
sys.write(JSON.stringify(convertToTSConfig(configParseResult, configFileName, sys), null, 4) + sys.newLine);
return sys.exit(ExitStatus.Success);
}
reportDiagnostic = updateReportDiagnostic(
sys,
reportDiagnostic,
configParseResult.options
);
if (isWatchSet(configParseResult.options)) {
if (reportWatchModeWithoutSysSupport(sys, reportDiagnostic)) return;
return createWatchOfConfigFile(
sys,
cb,
reportDiagnostic,
configParseResult,
commandLineOptions,
commandLine.watchOptions,
extendedConfigCache,
);
}
else if (isIncrementalCompilation(configParseResult.options)) {
performIncrementalCompilation(
sys,
cb,
reportDiagnostic,
configParseResult
);
}
else {
performCompilation(
sys,
cb,
reportDiagnostic,
configParseResult
);
}
}
else {
if (commandLineOptions.showConfig) {
// eslint-disable-next-line no-null/no-null
sys.write(JSON.stringify(convertToTSConfig(commandLine, combinePaths(currentDirectory, "tsconfig.json"), sys), null, 4) + sys.newLine);
return sys.exit(ExitStatus.Success);
}
reportDiagnostic = updateReportDiagnostic(
sys,
reportDiagnostic,
commandLineOptions
);
if (isWatchSet(commandLineOptions)) {
if (reportWatchModeWithoutSysSupport(sys, reportDiagnostic)) return;
return createWatchOfFilesAndCompilerOptions(
sys,
cb,
reportDiagnostic,
commandLine.fileNames,
commandLineOptions,
commandLine.watchOptions,
);
}
else if (isIncrementalCompilation(commandLineOptions)) {
performIncrementalCompilation(
sys,
cb,
reportDiagnostic,
{ ...commandLine, options: commandLineOptions }
);
}
else {
performCompilation(
sys,
cb,
reportDiagnostic,
{ ...commandLine, options: commandLineOptions }
);
}
}
}
export function isBuild(commandLineArgs: readonly string[]) {
if (commandLineArgs.length > 0 && commandLineArgs[0].charCodeAt(0) === CharacterCodes.minus) {
const firstOption = commandLineArgs[0].slice(commandLineArgs[0].charCodeAt(1) === CharacterCodes.minus ? 2 : 1).toLowerCase();
return firstOption === "build" || firstOption === "b";
}
return false;
}
export type ExecuteCommandLineCallbacks = (program: Program | EmitAndSemanticDiagnosticsBuilderProgram | ParsedCommandLine) => void;
export function executeCommandLine(
system: System,
cb: ExecuteCommandLineCallbacks,
commandLineArgs: readonly string[],
) {
if (isBuild(commandLineArgs)) {
const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(commandLineArgs.slice(1));
if (buildOptions.generateCpuProfile && system.enableCPUProfiler) {
system.enableCPUProfiler(buildOptions.generateCpuProfile, () => performBuild(
system,
cb,
buildOptions,
watchOptions,
projects,
errors
));
}
else {
return performBuild(
system,
cb,
buildOptions,
watchOptions,
projects,
errors
);
}
}
const commandLine = parseCommandLine(commandLineArgs, path => system.readFile(path));
if (commandLine.options.generateCpuProfile && system.enableCPUProfiler) {
system.enableCPUProfiler(commandLine.options.generateCpuProfile, () => executeCommandLineWorker(
system,
cb,
commandLine,
));
}
else {
return executeCommandLineWorker(system, cb, commandLine);
}
}
function reportWatchModeWithoutSysSupport(sys: System, reportDiagnostic: DiagnosticReporter) {
if (!sys.watchFile || !sys.watchDirectory) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return true;
}
return false;
}
function performBuild(
sys: System,
cb: ExecuteCommandLineCallbacks,
buildOptions: BuildOptions,
watchOptions: WatchOptions | undefined,
projects: string[],
errors: Diagnostic[]
) {
// Update to pretty if host supports it
const reportDiagnostic = updateReportDiagnostic(
sys,
createDiagnosticReporter(sys),
buildOptions
);
if (buildOptions.locale) {
validateLocaleAndSetLanguage(buildOptions.locale, sys, errors);
}
if (errors.length > 0) {
errors.forEach(reportDiagnostic);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (buildOptions.help) {
printVersion(sys);
printBuildHelp(sys, buildOpts);
return sys.exit(ExitStatus.Success);
}
if (projects.length === 0) {
printVersion(sys);
printBuildHelp(sys, buildOpts);
return sys.exit(ExitStatus.Success);
}
if (!sys.getModifiedTime || !sys.setModifiedTime || (buildOptions.clean && !sys.deleteFile)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--build"));
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (buildOptions.watch) {
if (reportWatchModeWithoutSysSupport(sys, reportDiagnostic)) return;
const buildHost = createSolutionBuilderWithWatchHost(
sys,
/*createProgram*/ undefined,
reportDiagnostic,
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
createWatchStatusReporter(sys, buildOptions)
);
updateSolutionBuilderHost(sys, cb, buildHost);
const builder = createSolutionBuilderWithWatch(buildHost, projects, buildOptions, watchOptions);
builder.build();
return builder;
}
const buildHost = createSolutionBuilderHost(
sys,
/*createProgram*/ undefined,
reportDiagnostic,
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
createReportErrorSummary(sys, buildOptions)
);
updateSolutionBuilderHost(sys, cb, buildHost);
const builder = createSolutionBuilder(buildHost, projects, buildOptions);
const exitStatus = buildOptions.clean ? builder.clean() : builder.build();
dumpTracingLegend(); // Will no-op if there hasn't been any tracing
return sys.exit(exitStatus);
}
function createReportErrorSummary(sys: System, options: CompilerOptions | BuildOptions): ReportEmitErrorSummary | undefined {
return shouldBePretty(sys, options) ?
errorCount => sys.write(getErrorSummaryText(errorCount, sys.newLine)) :
undefined;
}
function performCompilation(
sys: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
config: ParsedCommandLine
) {
const { fileNames, options, projectReferences } = config;
const host = createCompilerHostWorker(options, /*setParentPos*/ undefined, sys);
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
enableStatisticsAndTracing(sys, options, /*isBuildMode*/ false);
const programOptions: CreateProgramOptions = {
rootNames: fileNames,
options,
projectReferences,
host,
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config)
};
const program = createProgram(programOptions);
const exitStatus = emitFilesAndReportErrorsAndGetExitStatus(
program,
reportDiagnostic,
s => sys.write(s + sys.newLine),
createReportErrorSummary(sys, options)
);
reportStatistics(sys, program);
cb(program);
return sys.exit(exitStatus);
}
function performIncrementalCompilation(
sys: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
config: ParsedCommandLine
) {
const { options, fileNames, projectReferences } = config;
enableStatisticsAndTracing(sys, options, /*isBuildMode*/ false);
const host = createIncrementalCompilerHost(options, sys);
const exitStatus = ts.performIncrementalCompilation({
host,
system: sys,
rootNames: fileNames,
options,
configFileParsingDiagnostics: getConfigFileParsingDiagnostics(config),
projectReferences,
reportDiagnostic,
reportErrorSummary: createReportErrorSummary(sys, options),
afterProgramEmitAndDiagnostics: builderProgram => {
reportStatistics(sys, builderProgram.getProgram());
cb(builderProgram);
}
});
return sys.exit(exitStatus);
}
function updateSolutionBuilderHost(
sys: System,
cb: ExecuteCommandLineCallbacks,
buildHost: SolutionBuilderHostBase<EmitAndSemanticDiagnosticsBuilderProgram>
) {
updateCreateProgram(sys, buildHost);
buildHost.afterProgramEmitAndDiagnostics = program => {
reportStatistics(sys, program.getProgram());
cb(program);
};
buildHost.afterEmitBundle = cb;
}
function updateCreateProgram<T extends BuilderProgram>(sys: System, host: { createProgram: CreateProgram<T>; }) {
const compileUsingBuilder = host.createProgram;
host.createProgram = (rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences) => {
Debug.assert(rootNames !== undefined || (options === undefined && !!oldProgram));
if (options !== undefined) {
enableStatisticsAndTracing(sys, options, /*isBuildMode*/ true);
}
return compileUsingBuilder(rootNames, options, host, oldProgram, configFileParsingDiagnostics, projectReferences);
};
}
function updateWatchCompilationHost(
sys: System,
cb: ExecuteCommandLineCallbacks,
watchCompilerHost: WatchCompilerHost<EmitAndSemanticDiagnosticsBuilderProgram>,
) {
updateCreateProgram(sys, watchCompilerHost);
const emitFilesUsingBuilder = watchCompilerHost.afterProgramCreate!; // TODO: GH#18217
watchCompilerHost.afterProgramCreate = builderProgram => {
emitFilesUsingBuilder(builderProgram);
reportStatistics(sys, builderProgram.getProgram());
cb(builderProgram);
};
}
function createWatchStatusReporter(sys: System, options: CompilerOptions | BuildOptions) {
return ts.createWatchStatusReporter(sys, shouldBePretty(sys, options));
}
function createWatchOfConfigFile(
system: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
configParseResult: ParsedCommandLine,
optionsToExtend: CompilerOptions,
watchOptionsToExtend: WatchOptions | undefined,
extendedConfigCache: Map<ExtendedConfigCacheEntry>,
) {
const watchCompilerHost = createWatchCompilerHostOfConfigFile({
configFileName: configParseResult.options.configFilePath!,
optionsToExtend,
watchOptionsToExtend,
system,
reportDiagnostic,
reportWatchStatus: createWatchStatusReporter(system, configParseResult.options)
});
updateWatchCompilationHost(system, cb, watchCompilerHost);
watchCompilerHost.configFileParsingResult = configParseResult;
watchCompilerHost.extendedConfigCache = extendedConfigCache;
return createWatchProgram(watchCompilerHost);
}
function createWatchOfFilesAndCompilerOptions(
system: System,
cb: ExecuteCommandLineCallbacks,
reportDiagnostic: DiagnosticReporter,
rootFiles: string[],
options: CompilerOptions,
watchOptions: WatchOptions | undefined,
) {
const watchCompilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({
rootFiles,
options,
watchOptions,
system,
reportDiagnostic,
reportWatchStatus: createWatchStatusReporter(system, options)
});
updateWatchCompilationHost(system, cb, watchCompilerHost);
return createWatchProgram(watchCompilerHost);
}
function canReportDiagnostics(system: System, compilerOptions: CompilerOptions) {
return system === sys && (compilerOptions.diagnostics || compilerOptions.extendedDiagnostics);
}
function canTrace(system: System, compilerOptions: CompilerOptions) {
return system === sys && compilerOptions.generateTrace;
}
function enableStatisticsAndTracing(system: System, compilerOptions: CompilerOptions, isBuildMode: boolean) {
if (canReportDiagnostics(system, compilerOptions)) {
performance.enable(system);
}
if (canTrace(system, compilerOptions)) {
startTracing(isBuildMode ? "build" : "project",
compilerOptions.generateTrace!, compilerOptions.configFilePath);
}
}
function reportStatistics(sys: System, program: Program) {
const compilerOptions = program.getCompilerOptions();
if (canTrace(sys, compilerOptions)) {
tracing?.stopTracing();
}
let statistics: Statistic[];
if (canReportDiagnostics(sys, compilerOptions)) {
statistics = [];
const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
reportCountStatistic("Files", program.getSourceFiles().length);
const lineCounts = countLines(program);
const nodeCounts = countNodes(program);
if (compilerOptions.extendedDiagnostics) {
for (const key of arrayFrom(lineCounts.keys())) {
reportCountStatistic("Lines of " + key, lineCounts.get(key)!);
}
for (const key of arrayFrom(nodeCounts.keys())) {
reportCountStatistic("Nodes of " + key, nodeCounts.get(key)!);
}
}
else {
reportCountStatistic("Lines", reduceLeftIterator(lineCounts.values(), (sum, count) => sum + count, 0));
reportCountStatistic("Nodes", reduceLeftIterator(nodeCounts.values(), (sum, count) => sum + count, 0));
}
reportCountStatistic("Identifiers", program.getIdentifierCount());
reportCountStatistic("Symbols", program.getSymbolCount());
reportCountStatistic("Types", program.getTypeCount());
reportCountStatistic("Instantiations", program.getInstantiationCount());
if (memoryUsed >= 0) {
reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K");
}
const isPerformanceEnabled = performance.isEnabled();
const programTime = isPerformanceEnabled ? performance.getDuration("Program") : 0;
const bindTime = isPerformanceEnabled ? performance.getDuration("Bind") : 0;
const checkTime = isPerformanceEnabled ? performance.getDuration("Check") : 0;
const emitTime = isPerformanceEnabled ? performance.getDuration("Emit") : 0;
if (compilerOptions.extendedDiagnostics) {
const caches = program.getRelationCacheSizes();
reportCountStatistic("Assignability cache size", caches.assignable);
reportCountStatistic("Identity cache size", caches.identity);
reportCountStatistic("Subtype cache size", caches.subtype);
reportCountStatistic("Strict subtype cache size", caches.strictSubtype);
if (isPerformanceEnabled) {
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
}
}
else if (isPerformanceEnabled) {
// Individual component times.
// Note: To match the behavior of previous versions of the compiler, the reported parse time includes
// I/O read time and processing time for triple-slash references and module imports, and the reported
// emit time includes I/O write time. We preserve this behavior so we can accurately compare times.
reportTimeStatistic("I/O read", performance.getDuration("I/O Read"));
reportTimeStatistic("I/O write", performance.getDuration("I/O Write"));
reportTimeStatistic("Parse time", programTime);
reportTimeStatistic("Bind time", bindTime);
reportTimeStatistic("Check time", checkTime);
reportTimeStatistic("Emit time", emitTime);
}
if (isPerformanceEnabled) {
reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime);
}
reportStatistics();
if (!isPerformanceEnabled) {
sys.write(Diagnostics.Performance_timings_for_diagnostics_or_extendedDiagnostics_are_not_available_in_this_session_A_native_implementation_of_the_Web_Performance_API_could_not_be_found.message + "\n");
}
else {
performance.disable();
}
}
function reportStatistics() {
let nameSize = 0;
let valueSize = 0;
for (const { name, value } of statistics) {
if (name.length > nameSize) {
nameSize = name.length;
}
if (value.length > valueSize) {
valueSize = value.length;
}
}
for (const { name, value } of statistics) {
sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine);
}
}
function reportStatisticalValue(name: string, value: string) {
statistics.push({ name, value });
}
function reportCountStatistic(name: string, count: number) {
reportStatisticalValue(name, "" + count);
}
function reportTimeStatistic(name: string, time: number) {
reportStatisticalValue(name, (time / 1000).toFixed(2) + "s");
}
}
function writeConfigFile(
sys: System,
reportDiagnostic: DiagnosticReporter,
options: CompilerOptions,
fileNames: string[]
) {
const currentDirectory = sys.getCurrentDirectory();
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
if (sys.fileExists(file)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file));
}
else {
sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine));
const output: string[] = [sys.newLine, ...getHeader(sys,"Created a new tsconfig.json with:")];
output.push(getCompilerOptionsDiffValue(options, sys.newLine) + sys.newLine + sys.newLine);
output.push(`You can learn more at https://aka.ms/tsconfig.json` + sys.newLine);
for (const line of output) {
sys.write(line);
}
}
return;
}
}