mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-17 11:24:29 -05:00
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:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user