From f8c4301f14dad4ee4dd910231a67087a81212e33 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Wed, 30 May 2018 10:00:35 -0700 Subject: [PATCH] Add more errors; commandline help for --build; invalid flag combo detection --- src/compiler/commandLineParser.ts | 127 +++++++++++++++++++++++++ src/compiler/diagnosticMessages.json | 28 ++++++ src/compiler/tsbuild.ts | 64 +++++++++++++ src/compiler/tsc.ts | 137 +++------------------------ src/compiler/types.ts | 3 + 5 files changed, 236 insertions(+), 123 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 2f205f1e44e..fb0fcab4654 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -49,6 +49,14 @@ namespace ts { paramType: Diagnostics.FILE_OR_DIRECTORY, description: Diagnostics.Compile_the_project_given_the_path_to_its_configuration_file_or_to_a_folder_with_a_tsconfig_json, }, + { + name: "build", + type: "boolean", + shortName: "b", + showInSimplifiedHelpView: true, + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_one_or_more_projects_and_their_dependencies_if_out_of_date + }, { name: "pretty", type: "boolean", @@ -943,6 +951,125 @@ namespace ts { } + function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { + const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); + return diagnostic.messageText; + } + + /* @internal */ + export function printVersion() { + sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine); + } + + /* @internal */ + export function printHelp(optionsList: CommandLineOption[], syntaxPrefix = "") { + const output: string[] = []; + + // We want to align our "syntax" and "examples" commands to a certain margin. + const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; + const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; + let marginLength = Math.max(syntaxLength, examplesLength); + + // Build up the syntactic skeleton. + let syntax = makePadding(marginLength - syntaxLength); + syntax += `tsc ${syntaxPrefix}[${getDiagnosticText(Diagnostics.options)}] [${getDiagnosticText(Diagnostics.file)}...]`; + + output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); + output.push(sys.newLine + sys.newLine); + + // Build up the list of examples. + const padding = makePadding(marginLength); + output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); + output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); + output.push(padding + "tsc @args.txt" + sys.newLine); + output.push(padding + "tsc --build tsconfig.json" + sys.newLine); + output.push(sys.newLine); + + output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); + + // We want our descriptions to align at the same column in our output, + // so we keep track of the longest option usage string. + marginLength = 0; + const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. + const descriptionColumn: string[] = []; + + const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind + + for (const option of optionsList) { + // If an option lacks a description, + // it is not officially supported. + if (!option.description) { + continue; + } + + let usageText = " "; + if (option.shortName) { + usageText += "-" + option.shortName; + usageText += getParamType(option); + usageText += ", "; + } + + usageText += "--" + option.name; + usageText += getParamType(option); + + usageColumn.push(usageText); + let description: string; + + if (option.name === "lib") { + description = getDiagnosticText(option.description); + const element = (option).element; + const typeMap = >element.type; + optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); + } + else { + description = getDiagnosticText(option.description); + } + + descriptionColumn.push(description); + + // Set the new margin for the description column if necessary. + marginLength = Math.max(usageText.length, marginLength); + } + + // Special case that can't fit in the loop. + const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; + usageColumn.push(usageText); + descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); + marginLength = Math.max(usageText.length, marginLength); + + // Print out each row, aligning all the descriptions on the same column. + for (let i = 0; i < usageColumn.length; i++) { + const usage = usageColumn[i]; + const description = descriptionColumn[i]; + const kindsList = optionsDescriptionMap.get(description); + output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); + + if (kindsList) { + output.push(makePadding(marginLength + 4)); + for (const kind of kindsList) { + output.push(kind + " "); + } + output.push(sys.newLine); + } + } + + for (const line of output) { + sys.write(line); + } + return; + + function getParamType(option: CommandLineOption) { + if (option.paramType !== undefined) { + return " " + getDiagnosticText(option.paramType); + } + return ""; + } + + function makePadding(paddingLength: number): string { + return Array(paddingLength + 1).join(" "); + } + } + export type DiagnosticReporter = (diagnostic: Diagnostic) => void; /** * Reports config file diagnostics diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d5d8c459ee0..f76b0a39fbd 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3657,6 +3657,34 @@ "category": "Message", "code": 6362 }, + "Build one or more projects and their dependencies, if out-of-date": { + "category": "Message", + "code": 6363 + }, + "Delete the outputs of all projects": { + "category": "Message", + "code": 6364 + }, + "Enable verbose logging": { + "category": "Message", + "code": 6365 + }, + "Show what would be built (or deleted, if specified with --clean)": { + "category": "Message", + "code": 6366 + }, + "Build all projects, including those that appear to be up-to-date": { + "category": "Message", + "code": 6367 + }, + "Option '--build' must be the first command line argument.": { + "category": "Error", + "code": 6368 + }, + "Options '{0}' and '{1}' cannot be combined.": { + "category": "Error", + "code": 6369 + }, "Variable '{0}' implicitly has an '{1}' type.": { "category": "Error", diff --git a/src/compiler/tsbuild.ts b/src/compiler/tsbuild.ts index 978aa102c8a..665125b4dcc 100644 --- a/src/compiler/tsbuild.ts +++ b/src/compiler/tsbuild.ts @@ -338,11 +338,48 @@ namespace ts { }; } + const buildOpts: CommandLineOption[] = [ + { + name: "verbose", + shortName: "v", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Enable_verbose_logging, + type: "boolean" + }, + { + name: "dry", + shortName: "d", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean, + type: "boolean" + }, + { + name: "force", + shortName: "f", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date, + type: "boolean" + }, + { + name: "clean", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Delete_the_outputs_of_all_projects, + type: "boolean" + }, + { + name: "watch", + category: Diagnostics.Command_line_Options, + description: Diagnostics.Watch_input_files, + type: "boolean" + } + ]; + export function performBuild(host: CompilerHost, reportDiagnostic: DiagnosticReporter, args: string[]) { let verbose = false; let dry = false; let force = false; let clean = false; + let watch = false; const projects: string[] = []; for (const arg of args) { @@ -362,11 +399,38 @@ namespace ts { case "--clean": clean = true; continue; + case "--watch": + case "-w": + watch = true; + continue; + + case "--?": + case "-?": + case "--help": + return printHelp(buildOpts, "--build "); } // Not a flag, parse as filename addProject(arg); } + // Nonsensical combinations + if (clean && force) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force")); + return; + } + if (clean && verbose) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose")); + return; + } + if (clean && watch) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch")); + return; + } + if (watch && dry) { + reportDiagnostic(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry")); + return; + } + if (projects.length === 0) { // tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ." addProject("."); diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 62abd834180..9800d2e768a 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -12,11 +12,6 @@ namespace ts { return count; } - function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string { - const diagnostic = createCompilerDiagnostic.apply(undefined, arguments); - return diagnostic.messageText; - } - let reportDiagnostic = createDiagnosticReporter(sys); function updateReportDiagnostic(options: CompilerOptions) { if (shouldBePretty(options)) { @@ -46,6 +41,13 @@ namespace ts { return s; } + 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); + } + export function executeCommandLine(args: string[]): void { if ((args[0].toLowerCase() === "--build") || (args[0].toLowerCase() === "-b")) { return performBuild(createCompilerHost({}), createDiagnosticReporter(sys), args.slice(1)); @@ -53,6 +55,11 @@ namespace ts { const commandLine = parseCommandLine(args); + 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) { @@ -78,7 +85,7 @@ namespace ts { if (commandLine.options.help || commandLine.options.all) { printVersion(); - printHelp(!!commandLine.options.all); + printHelp(getOptionsForHelp(commandLine)); return sys.exit(ExitStatus.Success); } @@ -111,7 +118,7 @@ namespace ts { if (commandLine.fileNames.length === 0 && !configFileName) { printVersion(); - printHelp(!!commandLine.options.all); + printHelp(getOptionsForHelp(commandLine)); return sys.exit(ExitStatus.Success); } @@ -275,122 +282,6 @@ namespace ts { } } - function printVersion() { - sys.write(getDiagnosticText(Diagnostics.Version_0, version) + sys.newLine); - } - - function printHelp(showAllOptions: boolean) { - const output: string[] = []; - - // We want to align our "syntax" and "examples" commands to a certain margin. - const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length; - const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length; - let marginLength = Math.max(syntaxLength, examplesLength); - - // Build up the syntactic skeleton. - let syntax = makePadding(marginLength - syntaxLength); - syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]"; - - output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax)); - output.push(sys.newLine + sys.newLine); - - // Build up the list of examples. - const padding = makePadding(marginLength); - output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine); - output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine); - output.push(padding + "tsc @args.txt" + sys.newLine); - output.push(sys.newLine); - - output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine); - - // Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch") - const optsList = showAllOptions ? - sort(optionDeclarations, (a, b) => compareStringsCaseInsensitive(a.name, b.name)) : - filter(optionDeclarations.slice(), v => !!v.showInSimplifiedHelpView); - - // We want our descriptions to align at the same column in our output, - // so we keep track of the longest option usage string. - marginLength = 0; - const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. - const descriptionColumn: string[] = []; - - const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind - - for (const option of optsList) { - // If an option lacks a description, - // it is not officially supported. - if (!option.description) { - continue; - } - - let usageText = " "; - if (option.shortName) { - usageText += "-" + option.shortName; - usageText += getParamType(option); - usageText += ", "; - } - - usageText += "--" + option.name; - usageText += getParamType(option); - - usageColumn.push(usageText); - let description: string; - - if (option.name === "lib") { - description = getDiagnosticText(option.description); - const element = (option).element; - const typeMap = >element.type; - optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`)); - } - else { - description = getDiagnosticText(option.description); - } - - descriptionColumn.push(description); - - // Set the new margin for the description column if necessary. - marginLength = Math.max(usageText.length, marginLength); - } - - // Special case that can't fit in the loop. - const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">"; - usageColumn.push(usageText); - descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file)); - marginLength = Math.max(usageText.length, marginLength); - - // Print out each row, aligning all the descriptions on the same column. - for (let i = 0; i < usageColumn.length; i++) { - const usage = usageColumn[i]; - const description = descriptionColumn[i]; - const kindsList = optionsDescriptionMap.get(description); - output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine); - - if (kindsList) { - output.push(makePadding(marginLength + 4)); - for (const kind of kindsList) { - output.push(kind + " "); - } - output.push(sys.newLine); - } - } - - for (const line of output) { - sys.write(line); - } - return; - - function getParamType(option: CommandLineOption) { - if (option.paramType !== undefined) { - return " " + getDiagnosticText(option.paramType); - } - return ""; - } - - function makePadding(paddingLength: number): string { - return Array(paddingLength + 1).join(" "); - } - } - function writeConfigFile(options: CompilerOptions, fileNames: string[]) { const currentDirectory = sys.getCurrentDirectory(); const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json")); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index fff1224ba96..be57e9c6443 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4281,6 +4281,9 @@ namespace ts { allowUnusedLabels?: boolean; alwaysStrict?: boolean; // Always combine with strict property baseUrl?: string; + /** An error if set - this should only go through the -b pipeline and not actually be observed */ + /*@internal*/ + build?: boolean; charset?: string; checkJs?: boolean; /* @internal */ configFilePath?: string;