diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index a62f7238b69..253e38a5295 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -306,10 +306,8 @@ namespace ts { description: Diagnostics.Specify_ECMAScript_target_version_Colon_ES3_default_ES5_ES2015_ES2016_ES2017_ES2018_ES2019_ES2020_ES2021_or_ESNEXT, }; - /* @internal */ - export const optionDeclarations: CommandLineOption[] = [ + const commandOptionsWithoutBuild: CommandLineOption[] = [ // CommandLine only options - ...commonOptionsWithBuild, { name: "all", type: "boolean", @@ -1106,6 +1104,12 @@ namespace ts { }, ]; + /* @internal */ + export const optionDeclarations: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...commandOptionsWithoutBuild, + ]; + /* @internal */ export const semanticDiagnosticsOptionDeclarations: readonly CommandLineOption[] = optionDeclarations.filter(option => !!option.affectsSemanticDiagnostics); @@ -1126,9 +1130,7 @@ namespace ts { export const transpileOptionValueCompilerOptions: readonly CommandLineOption[] = optionDeclarations.filter(option => hasProperty(option, "transpileOptionValue")); - /* @internal */ - export const buildOpts: CommandLineOption[] = [ - ...commonOptionsWithBuild, + const commandOptionsOnlyBuild: CommandLineOption[] = [ { name: "verbose", shortName: "v", @@ -1158,6 +1160,12 @@ namespace ts { } ]; + /* @internal */ + export const buildOpts: CommandLineOption[] = [ + ...commonOptionsWithBuild, + ...commandOptionsOnlyBuild, + ]; + /* @internal */ export const typeAcquisitionDeclarations: CommandLineOption[] = [ { @@ -1217,9 +1225,14 @@ namespace ts { /* @internal */ export function getOptionsNameMap(): OptionsNameMap { - return optionsNameMapCache || (optionsNameMapCache = createOptionNameMap(optionDeclarations)); + return optionsNameMapCache ||= createOptionNameMap(optionDeclarations); } + const compilerOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_only_be_used_with_build, + getOptionsNameMap: getBuildOptionsNameMap + }; + /* @internal */ export const defaultInitCompilerOptions: CompilerOptions = { module: ModuleKind.CommonJS, @@ -1299,6 +1312,10 @@ namespace ts { createDiagnostics: (message: DiagnosticMessage, arg0: string, arg1?: string) => Diagnostic, unknownOptionErrorText?: string ) { + if (diagnostics.alternateMode?.getOptionsNameMap().optionsNameMap.has(unknownOption.toLowerCase())) { + return createDiagnostics(diagnostics.alternateMode.diagnostic, unknownOption); + } + const possibleOption = getSpellingSuggestion(unknownOption, diagnostics.optionDeclarations, getOptionName); return possibleOption ? createDiagnostics(diagnostics.unknownDidYouMeanDiagnostic, unknownOptionErrorText || unknownOption, possibleOption.name) : @@ -1464,6 +1481,7 @@ namespace ts { /*@internal*/ export const compilerOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: compilerOptionsAlternateMode, getOptionsNameMap, optionDeclarations, unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0, @@ -1505,7 +1523,13 @@ namespace ts { return buildOptionsNameMapCache || (buildOptionsNameMapCache = createOptionNameMap(buildOpts)); } + const buildOptionsAlternateMode: AlternateModeDiagnostics = { + diagnostic: Diagnostics.Compiler_option_0_may_not_be_used_with_build, + getOptionsNameMap + }; + const buildOptionsDidYouMeanDiagnostics: ParseCommandLineWorkerDiagnostics = { + alternateMode: buildOptionsAlternateMode, getOptionsNameMap: getBuildOptionsNameMap, optionDeclarations: buildOpts, unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 38f61060173..6a5765f4a9b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3938,6 +3938,14 @@ "category": "Error", "code": 5092 }, + "Compiler option '--{0}' may only be used with '--build'.": { + "category": "Error", + "code": 5093 + }, + "Compiler option '--{0}' may not be used with '--build'.": { + "category": "Error", + "code": 5094 + }, "Generates a sourcemap for each corresponding '.d.ts' file.": { "category": "Message", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8709515aa5a..63b20f49139 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6165,8 +6165,15 @@ namespace ts { type: ESMap; // an object literal mapping named values to actual values } + /* @internal */ + export interface AlternateModeDiagnostics { + diagnostic: DiagnosticMessage; + getOptionsNameMap: () => OptionsNameMap; + } + /* @internal */ export interface DidYouMeanOptionsDiagnostics { + alternateMode?: AlternateModeDiagnostics; optionDeclarations: CommandLineOption[]; unknownOptionDiagnostic: DiagnosticMessage, unknownDidYouMeanDiagnostic: DiagnosticMessage, diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index 79a4715b799..eea2b37c1b4 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -46,6 +46,23 @@ namespace ts { }); }); + it("Handles 'may only be used with --build' flags", () => { + const buildFlags = ["--clean", "--dry", "--force", "--verbose"]; + + assertParseResult(buildFlags, { + errors: buildFlags.map(buildFlag => ({ + messageText: `Compiler option '${buildFlag}' may only be used with '--build'.`, + category: Diagnostics.Compiler_option_0_may_only_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_only_be_used_with_build.code, + file: undefined, + start: undefined, + length: undefined + })), + fileNames: [], + options: {} + }); + }); + it("Handles 'did you mean?' for misspelt flags", () => { // --declarations --allowTS assertParseResult(["--declarations", "--allowTS"], { @@ -790,9 +807,9 @@ namespace ts { assertParseResult(["--listFilesOnly"], { errors: [{ - messageText: "Unknown build option '--listFilesOnly'.", - category: Diagnostics.Unknown_build_option_0.category, - code: Diagnostics.Unknown_build_option_0.code, + messageText: "Compiler option '--listFilesOnly' may not be used with '--build'.", + category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, file: undefined, start: undefined, length: undefined, @@ -863,9 +880,9 @@ namespace ts { assertParseResult(["--tsBuildInfoFile", "build.tsbuildinfo", "tests"], { errors: [{ - messageText: "Unknown build option '--tsBuildInfoFile'.", - category: Diagnostics.Unknown_build_option_0.category, - code: Diagnostics.Unknown_build_option_0.code, + messageText: "Compiler option '--tsBuildInfoFile' may not be used with '--build'.", + category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, file: undefined, start: undefined, length: undefined @@ -876,6 +893,24 @@ namespace ts { }); }); + it("reports other common 'may not be used with --build' flags", () => { + const buildFlags = ["--declaration", "--strict"]; + + assertParseResult(buildFlags, { + errors: buildFlags.map(buildFlag => ({ + messageText: `Compiler option '${buildFlag}' may not be used with '--build'.`, + category: Diagnostics.Compiler_option_0_may_not_be_used_with_build.category, + code: Diagnostics.Compiler_option_0_may_not_be_used_with_build.code, + file: undefined, + start: undefined, + length: undefined + })), + buildOptions: {}, + projects: ["."], + watchOptions: undefined, + }); + }); + describe("Combining options that make no sense together", () => { function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) { it(`--${flag1} and --${flag2} together is invalid`, () => {