Adds 'did you mean' to the CLI args parser (#35063)

* Adds did you mean to the CLI args parser

* Adds test coverage for the did you mean on CLI args

* Adds did you mean to convertOptionsFromJson

* Ensure tsconfig compiler flags also get 'did you mean?'
This commit is contained in:
Orta
2019-11-13 20:16:48 -05:00
committed by GitHub
parent f5bdd4daca
commit ef0cca7d12
8 changed files with 131 additions and 36 deletions

View File

@@ -1087,12 +1087,15 @@ namespace ts {
[option: string]: CompilerOptionsValue | undefined;
}
/** Tuple with error messages for 'unknown compiler option', 'option requires type' */
type ParseCommandLineWorkerDiagnostics = [DiagnosticMessage, DiagnosticMessage];
interface ParseCommandLineWorkerDiagnostics {
unknownOptionDiagnostic: DiagnosticMessage,
unknownDidYouMeanDiagnostic: DiagnosticMessage,
optionTypeMismatchDiagnostic: DiagnosticMessage
}
function parseCommandLineWorker(
getOptionNameMap: () => OptionNameMap,
[unknownOptionDiagnostic, optionTypeMismatchDiagnostic]: ParseCommandLineWorkerDiagnostics,
diagnostics: ParseCommandLineWorkerDiagnostics,
commandLine: readonly string[],
readFile?: (path: string) => string | undefined) {
const options = {} as OptionsBase;
@@ -1123,7 +1126,7 @@ namespace ts {
else {
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
if (!args[i] && opt.type !== "boolean") {
errors.push(createCompilerDiagnostic(optionTypeMismatchDiagnostic, opt.name));
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name));
}
switch (opt.type) {
@@ -1160,7 +1163,13 @@ namespace ts {
}
}
else {
errors.push(createCompilerDiagnostic(unknownOptionDiagnostic, s));
const possibleOption = getSpellingSuggestion(s, optionDeclarations, opt => `--${opt.name}`);
if (possibleOption) {
errors.push(createCompilerDiagnostic(diagnostics.unknownDidYouMeanDiagnostic, s, possibleOption.name));
}
else {
errors.push(createCompilerDiagnostic(diagnostics.unknownOptionDiagnostic, s));
}
}
}
else {
@@ -1203,11 +1212,13 @@ namespace ts {
}
}
const compilerOptionsDefaultDiagnostics = {
unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Compiler_option_0_expects_an_argument
};
export function parseCommandLine(commandLine: readonly string[], readFile?: (path: string) => string | undefined): ParsedCommandLine {
return parseCommandLineWorker(getOptionNameMap, [
Diagnostics.Unknown_compiler_option_0,
Diagnostics.Compiler_option_0_expects_an_argument
], commandLine, readFile);
return parseCommandLineWorker(getOptionNameMap, compilerOptionsDefaultDiagnostics, commandLine, readFile);
}
/** @internal */
@@ -1239,10 +1250,11 @@ namespace ts {
export function parseBuildCommand(args: readonly string[]): ParsedBuildCommand {
let buildOptionNameMap: OptionNameMap | undefined;
const returnBuildOptionNameMap = () => (buildOptionNameMap || (buildOptionNameMap = createOptionNameMap(buildOpts)));
const { options, fileNames: projects, errors } = parseCommandLineWorker(returnBuildOptionNameMap, [
Diagnostics.Unknown_build_option_0,
Diagnostics.Build_option_0_requires_a_value_of_type_1
], args);
const { options, fileNames: projects, errors } = parseCommandLineWorker(returnBuildOptionNameMap, {
unknownOptionDiagnostic: Diagnostics.Unknown_build_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_build_option_0_Did_you_mean_1,
optionTypeMismatchDiagnostic: Diagnostics.Build_option_0_requires_a_value_of_type_1
}, args);
const buildOptions = options as BuildOptions;
if (projects.length === 0) {
@@ -1389,19 +1401,28 @@ namespace ts {
name: "compilerOptions",
type: "object",
elementOptions: commandLineOptionsToMap(optionDeclarations),
extraKeyDiagnosticMessage: Diagnostics.Unknown_compiler_option_0
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_compiler_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1
},
},
{
name: "typingOptions",
type: "object",
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
},
},
{
name: "typeAcquisition",
type: "object",
elementOptions: commandLineOptionsToMap(typeAcquisitionDeclarations),
extraKeyDiagnosticMessage: Diagnostics.Unknown_type_acquisition_option_0
extraKeyDiagnostics: {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1
}
},
{
name: "extends",
@@ -1507,7 +1528,7 @@ namespace ts {
function convertObjectLiteralExpressionToJson(
node: ObjectLiteralExpression,
knownOptions: Map<CommandLineOption> | undefined,
extraKeyDiagnosticMessage: DiagnosticMessage | undefined,
extraKeyDiagnostics: DidYouMeanOptionalDiagnostics | undefined,
parentOption: string | undefined
): any {
const result: any = returnValue ? {} : undefined;
@@ -1527,8 +1548,19 @@ namespace ts {
const textOfKey = getTextOfPropertyName(element.name);
const keyText = textOfKey && unescapeLeadingUnderscores(textOfKey);
const option = keyText && knownOptions ? knownOptions.get(keyText) : undefined;
if (keyText && extraKeyDiagnosticMessage && !option) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnosticMessage, keyText));
if (keyText && extraKeyDiagnostics && !option) {
if (knownOptions) {
const possibleOption = getSpellingSuggestion(keyText, arrayFrom(knownOptions.keys()), identity);
if (possibleOption) {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownDidYouMeanDiagnostic, keyText, possibleOption));
}
else {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText));
}
}
else {
errors.push(createDiagnosticForNodeInSourceFile(sourceFile, element.name, extraKeyDiagnostics.unknownOptionDiagnostic, keyText));
}
}
const value = convertPropertyValueToJson(element.initializer, option);
if (typeof keyText !== "undefined") {
@@ -1630,9 +1662,9 @@ namespace ts {
// vs what we set in the json
// If need arises, we can modify this interface and callbacks as needed
if (option) {
const { elementOptions, extraKeyDiagnosticMessage, name: optionName } = <TsConfigOnlyOption>option;
const { elementOptions, extraKeyDiagnostics, name: optionName } = <TsConfigOnlyOption>option;
return convertObjectLiteralExpressionToJson(objectLiteralExpression,
elementOptions, extraKeyDiagnosticMessage, optionName);
elementOptions, extraKeyDiagnostics, optionName);
}
else {
return convertObjectLiteralExpressionToJson(
@@ -2468,7 +2500,7 @@ namespace ts {
basePath: string, errors: Push<Diagnostic>, configFileName?: string): CompilerOptions {
const options = getDefaultCompilerOptions(configFileName);
convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, Diagnostics.Unknown_compiler_option_0, errors);
convertOptionsFromJson(optionDeclarations, jsonOptions, basePath, options, compilerOptionsDefaultDiagnostics, errors);
if (configFileName) {
options.configFilePath = normalizeSlashes(configFileName);
}
@@ -2484,13 +2516,19 @@ namespace ts {
const options = getDefaultTypeAcquisition(configFileName);
const typeAcquisition = convertEnableAutoDiscoveryToEnable(jsonOptions);
convertOptionsFromJson(typeAcquisitionDeclarations, typeAcquisition, basePath, options, Diagnostics.Unknown_type_acquisition_option_0, errors);
const diagnostics = {
unknownOptionDiagnostic: Diagnostics.Unknown_type_acquisition_option_0,
unknownDidYouMeanDiagnostic: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1 ,
};
convertOptionsFromJson(typeAcquisitionDeclarations, typeAcquisition, basePath, options, diagnostics, errors);
return options;
}
function convertOptionsFromJson(optionDeclarations: readonly CommandLineOption[], jsonOptions: any, basePath: string,
defaultOptions: CompilerOptions | TypeAcquisition, diagnosticMessage: DiagnosticMessage, errors: Push<Diagnostic>) {
defaultOptions: CompilerOptions | TypeAcquisition, diagnostics: DidYouMeanOptionalDiagnostics, errors: Push<Diagnostic>) {
if (!jsonOptions) {
return;
@@ -2504,7 +2542,13 @@ namespace ts {
defaultOptions[opt.name] = convertJsonOption(opt, jsonOptions[id], basePath, errors);
}
else {
errors.push(createCompilerDiagnostic(diagnosticMessage, id));
const possibleOption = getSpellingSuggestion(id, <CommandLineOption[]>optionDeclarations, opt => opt.name);
if (possibleOption) {
errors.push(createCompilerDiagnostic(diagnostics.unknownDidYouMeanDiagnostic, id, possibleOption.name));
}
else {
errors.push(createCompilerDiagnostic(diagnostics.unknownOptionDiagnostic, id));
}
}
}
}

View File

@@ -3177,6 +3177,10 @@
"category": "Error",
"code": 5024
},
"Unknown compiler option '{0}'. Did you mean '{1}'?": {
"category": "Error",
"code": 5025
},
"Could not write file '{0}': {1}.": {
"category": "Error",
"code": 5033
@@ -3297,6 +3301,10 @@
"category": "Error",
"code": 5076
},
"Unknown build option '{0}'. Did you mean '{1}'?": {
"category": "Error",
"code": 5077
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@@ -4747,7 +4755,10 @@
"category": "Error",
"code": 17017
},
"Unknown type acquisition option '{0}'. Did you mean '{1}'?": {
"category": "Error",
"code": 17018
},
"Circularity detected while resolving configuration: {0}": {
"category": "Error",
"code": 18000

View File

@@ -5199,11 +5199,17 @@ namespace ts {
type: Map<number | string>; // an object literal mapping named values to actual values
}
/* @internal */
export interface DidYouMeanOptionalDiagnostics {
unknownOptionDiagnostic: DiagnosticMessage,
unknownDidYouMeanDiagnostic: DiagnosticMessage,
}
/* @internal */
export interface TsConfigOnlyOption extends CommandLineOptionBase {
type: "object";
elementOptions?: Map<CommandLineOption>;
extraKeyDiagnosticMessage?: DiagnosticMessage;
extraKeyDiagnostics?: DidYouMeanOptionalDiagnostics;
}
/* @internal */