mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06: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:
parent
f5bdd4daca
commit
ef0cca7d12
@ -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 */
|
||||
|
||||
@ -40,6 +40,33 @@ namespace ts {
|
||||
});
|
||||
});
|
||||
|
||||
it("Handles 'did you mean?' for misspelt flags", () => {
|
||||
// --declarations --allowTS
|
||||
assertParseResult(["--declarations", "--allowTS"], {
|
||||
errors: [
|
||||
{
|
||||
messageText:"Unknown compiler option '--declarations'. Did you mean 'declaration'?",
|
||||
category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category,
|
||||
code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
},
|
||||
{
|
||||
messageText: "Unknown compiler option '--allowTS'. Did you mean 'allowJs'?",
|
||||
category: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.category,
|
||||
code: Diagnostics.Unknown_compiler_option_0_Did_you_mean_1.code,
|
||||
file: undefined,
|
||||
start: undefined,
|
||||
length: undefined
|
||||
}
|
||||
],
|
||||
fileNames: [],
|
||||
options: {}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("Parse multiple options of library flags ", () => {
|
||||
// --lib es5,es2015.symbol.wellknown 0.ts
|
||||
assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "0.ts"],
|
||||
@ -556,4 +583,6 @@ namespace ts {
|
||||
verifyInvalidCombination("watch", "dry");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -111,8 +111,8 @@ namespace ts {
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
category: Diagnostics.Unknown_type_acquisition_option_0.category,
|
||||
code: Diagnostics.Unknown_type_acquisition_option_0.code,
|
||||
category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category,
|
||||
code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code,
|
||||
file: undefined,
|
||||
start: 0,
|
||||
length: 0,
|
||||
@ -206,8 +206,8 @@ namespace ts {
|
||||
},
|
||||
errors: [
|
||||
{
|
||||
category: Diagnostics.Unknown_type_acquisition_option_0.category,
|
||||
code: Diagnostics.Unknown_type_acquisition_option_0.code,
|
||||
category: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.category,
|
||||
code: Diagnostics.Unknown_type_acquisition_option_0_Did_you_mean_1.code,
|
||||
file: undefined,
|
||||
start: 0,
|
||||
length: 0,
|
||||
|
||||
@ -273,6 +273,11 @@ namespace ts.tscWatch {
|
||||
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0, option);
|
||||
}
|
||||
|
||||
export function getUnknownDidYouMeanCompilerOption(program: Program, configFile: File, option: string, didYouMean: string) {
|
||||
const quotedOption = `"${option}"`;
|
||||
return getDiagnosticOfFile(program.getCompilerOptions().configFile!, configFile.content.indexOf(quotedOption), quotedOption.length, Diagnostics.Unknown_compiler_option_0_Did_you_mean_1, option, didYouMean);
|
||||
}
|
||||
|
||||
export function getDiagnosticModuleNotFoundOfFile(program: Program, file: File, moduleName: string) {
|
||||
const quotedModuleName = `"${moduleName}"`;
|
||||
return getDiagnosticOfFileFromProgram(program, file.path, file.content.indexOf(quotedModuleName), quotedModuleName.length, Diagnostics.Cannot_find_module_0, moduleName);
|
||||
|
||||
@ -757,7 +757,7 @@ namespace ts.tscWatch {
|
||||
const watch = createWatchOfConfigFile(configFile.path, host);
|
||||
checkOutputErrorsInitial(host, [
|
||||
getUnknownCompilerOption(watch(), configFile, "foo"),
|
||||
getUnknownCompilerOption(watch(), configFile, "allowJS")
|
||||
getUnknownDidYouMeanCompilerOption(watch(), configFile, "allowJS", "allowJs")
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -496,14 +496,14 @@ declare module '@custom/plugin' {
|
||||
});
|
||||
|
||||
describe("unittests:: tsserver:: Project Errors for Configure file diagnostics events", () => {
|
||||
function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string): ConfigFileDiagnostic {
|
||||
const d = Diagnostics.Unknown_compiler_option_0;
|
||||
function getUnknownCompilerOptionDiagnostic(configFile: File, prop: string, didYouMean?: string): ConfigFileDiagnostic {
|
||||
const d = didYouMean ? Diagnostics.Unknown_compiler_option_0_Did_you_mean_1 : Diagnostics.Unknown_compiler_option_0;
|
||||
const start = configFile.content.indexOf(prop) - 1; // start at "prop"
|
||||
return {
|
||||
fileName: configFile.path,
|
||||
start,
|
||||
length: prop.length + 2,
|
||||
messageText: formatStringFromArgs(d.message, [prop]),
|
||||
messageText: formatStringFromArgs(d.message, didYouMean ? [prop, didYouMean] : [prop]),
|
||||
category: d.category,
|
||||
code: d.code,
|
||||
reportsUnnecessary: undefined
|
||||
@ -543,7 +543,7 @@ declare module '@custom/plugin' {
|
||||
openFilesForSession([file], serverEventManager.session);
|
||||
serverEventManager.checkSingleConfigFileDiagEvent(configFile.path, file.path, [
|
||||
getUnknownCompilerOptionDiagnostic(configFile, "foo"),
|
||||
getUnknownCompilerOptionDiagnostic(configFile, "allowJS")
|
||||
getUnknownCompilerOptionDiagnostic(configFile, "allowJS", "allowJs")
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user