Codefix: Don’t return a fixId if there’s definitely nothing else that can be fixed (#35765)

* Start fixing fixId

* Fix tests

* Add comment

* Fix unit tests, remove fixAllDescription when unavailable

* Add codeFixAllAvailable to fourslash harness
This commit is contained in:
Andrew Branch
2020-01-16 10:07:37 -08:00
committed by GitHub
parent eeff036519
commit 797c5362a2
23 changed files with 97 additions and 58 deletions

View File

@@ -10,7 +10,7 @@ namespace ts.codefix {
: getLocaleSpecificMessage(diag);
}
export function createCodeFixActionNoFixId(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) {
export function createCodeFixActionWithoutFixAll(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) {
return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined);
}
@@ -38,8 +38,24 @@ namespace ts.codefix {
return arrayFrom(errorCodeToFixes.keys());
}
function removeFixIdIfFixAllUnavailable(registration: CodeFixRegistration, diagnostics: Diagnostic[]) {
const { errorCodes } = registration;
let maybeFixableDiagnostics = 0;
for (const diag of diagnostics) {
if (contains(errorCodes, diag.code)) maybeFixableDiagnostics++;
if (maybeFixableDiagnostics > 1) break;
}
const fixAllUnavailable = maybeFixableDiagnostics < 2;
return ({ fixId, fixAllDescription, ...action }: CodeFixAction): CodeFixAction => {
return fixAllUnavailable ? action : { ...action, fixId, fixAllDescription };
};
}
export function getFixes(context: CodeFixContext): readonly CodeFixAction[] {
return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context));
const diagnostics = getDiagnostics(context);
const registrations = errorCodeToFixes.get(String(context.errorCode));
return flatMap(registrations, f => map(f.getCodeActions(context), removeFixIdIfFixAllUnavailable(f, diagnostics)));
}
export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions {
@@ -65,11 +81,15 @@ namespace ts.codefix {
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
}
export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void {
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
export function eachDiagnostic(context: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void {
for (const diag of getDiagnostics(context)) {
if (contains(errorCodes, diag.code)) {
cb(diag as DiagnosticWithLocation);
}
}
}
function getDiagnostics({ program, sourceFile, cancellationToken }: CodeFixContextBase) {
return program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken));
}
}

View File

@@ -68,7 +68,9 @@ namespace ts.codefix {
makeChange(t, errorCode, sourceFile, checker, expression, fixedDeclarations);
}
});
return createCodeFixActionNoFixId(
// No fix-all because it will already be included once with the use site fix,
// and for simplicity the fix-all doesnt let the user choose between use-site and declaration-site fixes.
return createCodeFixActionWithoutFixAll(
"addMissingAwaitToInitializer",
initializerChanges,
awaitableInitializers.initializers.length === 1

View File

@@ -30,7 +30,7 @@ namespace ts.codefix {
if (forInitializer) return applyChange(changeTracker, forInitializer, sourceFile, fixedNodes);
const parent = token.parent;
if (isBinaryExpression(parent) && isExpressionStatement(parent.parent)) {
if (isBinaryExpression(parent) && parent.operatorToken.kind === SyntaxKind.EqualsToken && isExpressionStatement(parent.parent)) {
return applyChange(changeTracker, token, sourceFile, fixedNodes);
}
@@ -104,6 +104,8 @@ namespace ts.codefix {
return every([expression.left, expression.right], expression => expressionCouldBeVariableDeclaration(expression, checker));
}
return isIdentifier(expression.left) && !checker.getSymbolAtLocation(expression.left);
return expression.operatorToken.kind === SyntaxKind.EqualsToken
&& isIdentifier(expression.left)
&& !checker.getSymbolAtLocation(expression.left);
}
}

View File

@@ -13,7 +13,7 @@ namespace ts.codefix {
}
});
// No support for fix-all since this applies to the whole file at once anyway.
return [createCodeFixActionNoFixId("convertToEs6Module", changes, Diagnostics.Convert_to_ES6_module)];
return [createCodeFixActionWithoutFixAll("convertToEs6Module", changes, Diagnostics.Convert_to_ES6_module)];
},
});

View File

@@ -18,7 +18,7 @@ namespace ts.codefix {
const fixes: CodeFixAction[] = [
// fixId unnecessary because adding `// @ts-nocheck` even once will ignore every error in the file.
createCodeFixActionNoFixId(
createCodeFixActionWithoutFixAll(
fixName,
[createFileTextChanges(sourceFile.fileName, [
createTextChange(sourceFile.checkJsDirective

View File

@@ -255,7 +255,7 @@ namespace ts.codefix {
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(declSourceFile, classDeclaration, indexSignature));
// No fixId here because code-fix-all currently only works on adding individual named properties.
return createCodeFixActionNoFixId(fixName, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]);
return createCodeFixActionWithoutFixAll(fixName, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]);
}
function getActionForMethodDeclaration(

View File

@@ -13,7 +13,7 @@ namespace ts.codefix {
}
const changes = textChanges.ChangeTracker.with(context, changeTracker => doChange(changeTracker, configFile));
return [createCodeFixActionNoFixId(fixId, changes, Diagnostics.Enable_the_experimentalDecorators_option_in_your_configuration_file)];
return [createCodeFixActionWithoutFixAll(fixId, changes, Diagnostics.Enable_the_experimentalDecorators_option_in_your_configuration_file)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes) => {

View File

@@ -14,7 +14,7 @@ namespace ts.codefix {
doChange(changeTracker, configFile)
);
return [
createCodeFixActionNoFixId(fixID, changes, Diagnostics.Enable_the_jsx_flag_in_your_configuration_file)
createCodeFixActionWithoutFixAll(fixID, changes, Diagnostics.Enable_the_jsx_flag_in_your_configuration_file)
];
},
fixIds: [fixID],

View File

@@ -26,7 +26,7 @@ namespace ts.codefix {
function createAction(context: CodeFixContext, sourceFile: SourceFile, node: Node, replacement: Node): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, node, replacement));
return createCodeFixActionNoFixId(fixName, changes, [Diagnostics.Replace_import_with_0, changes[0].textChanges[0].newText]);
return createCodeFixActionWithoutFixAll(fixName, changes, [Diagnostics.Replace_import_with_0, changes[0].textChanges[0].newText]);
}
registerCodeFix({
@@ -89,7 +89,7 @@ namespace ts.codefix {
if (isExpression(expr) && !(isNamedDeclaration(expr.parent) && expr.parent.name === expr)) {
const sourceFile = context.sourceFile;
const changes = textChanges.ChangeTracker.with(context, t => t.replaceNode(sourceFile, expr, createPropertyAccess(expr, "default"), {}));
fixes.push(createCodeFixActionNoFixId(fixName, changes, Diagnostics.Use_synthetic_default_member));
fixes.push(createCodeFixActionWithoutFixAll(fixName, changes, Diagnostics.Use_synthetic_default_member));
}
return fixes;
}

View File

@@ -1,6 +1,7 @@
/* @internal */
namespace ts.codefix {
export const importFixId = "fixMissingImport";
export const importFixName = "import";
const importFixId = "fixMissingImport";
const errorCodes: readonly number[] = [
Diagnostics.Cannot_find_name_0.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
@@ -542,7 +543,7 @@ namespace ts.codefix {
const changes = textChanges.ChangeTracker.with(context, tracker => {
diag = codeActionForFixWorker(tracker, sourceFile, symbolName, fix, quotePreference);
});
return createCodeFixAction("import", changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
return createCodeFixAction(importFixName, changes, diag, importFixId, Diagnostics.Add_all_missing_imports);
}
function codeActionForFixWorker(changes: textChanges.ChangeTracker, sourceFile: SourceFile, symbolName: string, fix: ImportFix, quotePreference: QuotePreference): DiagnosticAndArguments {
switch (fix.kind) {