diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index dc21a358132..2ad1bfb8eea 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -5155,10 +5155,6 @@ "category": "Message", "code": 90008 }, - "Remove destructuring": { - "category": "Message", - "code": 90009 - }, "Remove variable statement": { "category": "Message", "code": 90010 @@ -5275,6 +5271,14 @@ "category": "Message", "code": 90038 }, + "Remove unused destructuring declaration": { + "category": "Message", + "code": 90039 + }, + "Remove unused declarations for: '{0}'": { + "category": "Message", + "code": 90041 + }, "Declare a private field named '{0}'.": { "category": "Message", "code": 90053 diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 4aa099cb379..9467b6a9b0a 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -34,18 +34,33 @@ namespace ts.codefix { const changes = textChanges.ChangeTracker.with(context, t => t.delete(sourceFile, importDecl)); return [createDeleteFix(changes, [Diagnostics.Remove_import_from_0, showModuleSpecifier(importDecl)])]; } - const delDestructure = textChanges.ChangeTracker.with(context, t => - tryDeleteFullDestructure(token, t, sourceFile, checker, sourceFiles, /*isFixAll*/ false)); - if (delDestructure.length) { - return [createDeleteFix(delDestructure, Diagnostics.Remove_destructuring)]; + + if (isObjectBindingPattern(token.parent)) { + if (isParameter(token.parent.parent)) { + const elements = token.parent.elements; + const diagnostic: [DiagnosticMessage, string] = [ + elements.length > 1 ? Diagnostics.Remove_unused_declarations_for_Colon_0 : Diagnostics.Remove_unused_declaration_for_Colon_0, + map(elements, e => e.getText(sourceFile)).join(", ") + ]; + return [ + createDeleteFix(textChanges.ChangeTracker.with(context, t => + deleteDestructuringElements(t, sourceFile, token.parent)), diagnostic) + ]; + } + return [ + createDeleteFix(textChanges.ChangeTracker.with(context, t => + t.delete(sourceFile, token.parent.parent)), Diagnostics.Remove_unused_destructuring_declaration) + ]; } - const delVar = textChanges.ChangeTracker.with(context, t => tryDeleteFullVariableStatement(sourceFile, token, t)); - if (delVar.length) { - return [createDeleteFix(delVar, Diagnostics.Remove_variable_statement)]; + + if (canDeleteEntireVariableStatement(sourceFile, token)) { + return [ + createDeleteFix(textChanges.ChangeTracker.with(context, t => + deleteEntireVariableStatement(t, sourceFile, token.parent)), Diagnostics.Remove_variable_statement) + ]; } const result: CodeFixAction[] = []; - if (token.kind === SyntaxKind.InferKeyword) { const changes = textChanges.ChangeTracker.with(context, t => changeInferToUnknown(t, sourceFile, token)); const name = cast(token.parent, isInferTypeNode).typeParameter.name.text; @@ -79,7 +94,9 @@ namespace ts.codefix { tryPrefixDeclaration(changes, diag.code, sourceFile, token); break; case fixIdDelete: { - if (token.kind === SyntaxKind.InferKeyword) break; // Can't delete + if (token.kind === SyntaxKind.InferKeyword) { + break; // Can't delete + } const importDecl = tryGetFullImport(token); if (importDecl) { changes.delete(sourceFile, importDecl); @@ -90,8 +107,18 @@ namespace ts.codefix { else if (token.kind === SyntaxKind.LessThanToken) { deleteTypeParameters(changes, sourceFile, token); } - else if (!tryDeleteFullDestructure(token, changes, sourceFile, checker, sourceFiles, /*isFixAll*/ true) && - !tryDeleteFullVariableStatement(sourceFile, token, changes)) { + else if (isObjectBindingPattern(token.parent)) { + if (isParameter(token.parent.parent)) { + deleteDestructuringElements(changes, sourceFile, token.parent); + } + else { + changes.delete(sourceFile, token.parent.parent); + } + } + else if (canDeleteEntireVariableStatement(sourceFile, token)) { + deleteEntireVariableStatement(changes, sourceFile, token.parent); + } + else { tryDeleteDeclaration(sourceFile, token, changes, checker, sourceFiles, /*isFixAll*/ true); } break; @@ -125,25 +152,16 @@ namespace ts.codefix { return token.kind === SyntaxKind.ImportKeyword ? tryCast(token.parent, isImportDeclaration) : undefined; } - function tryDeleteFullDestructure(token: Node, changes: textChanges.ChangeTracker, sourceFile: SourceFile, checker: TypeChecker, sourceFiles: readonly SourceFile[], isFixAll: boolean): boolean { - if (token.kind !== SyntaxKind.OpenBraceToken || !isObjectBindingPattern(token.parent)) return false; - const decl = token.parent.parent; - if (decl.kind === SyntaxKind.Parameter) { - tryDeleteParameter(changes, sourceFile, decl, checker, sourceFiles, isFixAll); - } - else { - changes.delete(sourceFile, decl); - } - return true; + function canDeleteEntireVariableStatement(sourceFile: SourceFile, token: Node): boolean { + return isVariableDeclarationList(token.parent) && first(token.parent.getChildren(sourceFile)) === token; } - function tryDeleteFullVariableStatement(sourceFile: SourceFile, token: Node, changes: textChanges.ChangeTracker): boolean { - const declarationList = tryCast(token.parent, isVariableDeclarationList); - if (declarationList && declarationList.getChildren(sourceFile)[0] === token) { - changes.delete(sourceFile, declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList); - return true; - } - return false; + function deleteEntireVariableStatement(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: VariableDeclarationList) { + changes.delete(sourceFile, node.parent.kind === SyntaxKind.VariableStatement ? node.parent : node); + } + + function deleteDestructuringElements(changes: textChanges.ChangeTracker, sourceFile: SourceFile, node: ObjectBindingPattern) { + forEach(node.elements, n => changes.delete(sourceFile, n)); } function tryPrefixDeclaration(changes: textChanges.ChangeTracker, errorCode: number, sourceFile: SourceFile, token: Node): void { @@ -205,7 +223,7 @@ namespace ts.codefix { } } - function tryDeleteParameter(changes: textChanges.ChangeTracker, sourceFile: SourceFile, p: ParameterDeclaration, checker: TypeChecker, sourceFiles: readonly SourceFile[], isFixAll: boolean): void { + function tryDeleteParameter(changes: textChanges.ChangeTracker, sourceFile: SourceFile, p: ParameterDeclaration, checker: TypeChecker, sourceFiles: readonly SourceFile[], isFixAll = false): void { if (mayDeleteParameter(p, checker, isFixAll)) { if (p.modifiers && p.modifiers.length > 0 && (!isIdentifier(p.name) || FindAllReferences.Core.isSymbolReferencedInFile(p.name, checker, sourceFile))) { diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts index b169ffb8848..92585714eec 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete.ts @@ -50,7 +50,7 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: `import { used1 } from "foo"; import { used2 } from "foo"; diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_js.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_js.ts index a6251ffb19d..7920810cacf 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_js.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_js.ts @@ -38,7 +38,7 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: `/** Parameter doc comment */ function f() {} diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_paramInFunction.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_paramInFunction.ts index 80b190700bf..4a5fdc78bca 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_paramInFunction.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_all_delete_paramInFunction.ts @@ -5,6 +5,6 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: "export {};\n", }); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts index 61ff1211c7c..945f79bc010 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_deleteWrite.ts @@ -14,7 +14,7 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: ` export class C { diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts index 4f3f9a3e61e..71430d0a7bc 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused.ts @@ -7,7 +7,7 @@ ////const { x, y } = o; verify.codeFix({ - description: "Remove destructuring", + description: ts.Diagnostics.Remove_unused_destructuring_declaration.message, newFileContent: `export {}; `, diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts index c9d4f62f2db..5a16f374f34 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_all.ts @@ -12,11 +12,11 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: `const { a } = o; a; -export function f({ a }) { +export function f({ a }, { }) { a; }`, }); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts index 27103357d3d..fab0d1795cb 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_for.ts @@ -6,7 +6,7 @@ ////for (const { x } of o) {} verify.codeFix({ - description: "Remove destructuring", + description: ts.Diagnostics.Remove_unused_destructuring_declaration.message, newFileContent: `for (const {} of o) {}`, }); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts index 73fca113928..b830f5a90b5 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_allUnused_nested.ts @@ -7,7 +7,7 @@ ////const { x: { a, b } } = o; verify.codeFix({ - description: "Remove destructuring", + description: ts.Diagnostics.Remove_unused_destructuring_declaration.message, newFileContent: `export {}; const { } = o;`, diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts index e0783706085..e885054f359 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructure_partlyUnused.ts @@ -38,7 +38,7 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: `{ const { x } = o; diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_destructuring_elements.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_destructuring_elements.ts new file mode 100644 index 00000000000..060354a0422 --- /dev/null +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_destructuring_elements.ts @@ -0,0 +1,17 @@ +/// + +// @noUnusedLocals: true +// @noUnusedParameters: true + +////export function f({ x, y }, a) { +//// a; +////} + +verify.codeFix({ + description: [ts.Diagnostics.Remove_unused_declarations_for_Colon_0.message, "x, y"], + index: 0, + newFileContent: +`export function f({ }, a) { + a; +}`, +}); diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_parameter_all.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_parameter_all.ts index a55e867634a..16c9e6b4815 100644 --- a/tests/cases/fourslash/codeFixUnusedIdentifier_parameter_all.ts +++ b/tests/cases/fourslash/codeFixUnusedIdentifier_parameter_all.ts @@ -4,7 +4,7 @@ // @noUnusedParameters: true ////function f(a, b, { x, y }) { b; } -////f(0, 1, 2); +////f(0, 1, { x: 1, y: 1 }); //// ////class C { //// m(a, b, c) { b; } @@ -23,10 +23,10 @@ verify.codeFixAll({ fixId: "unusedIdentifier_delete", - fixAllDescription: "Delete all unused declarations", + fixAllDescription: ts.Diagnostics.Delete_all_unused_declarations.message, newFileContent: -`function f(b) { b; } -f(1); +`function f(b, { }) { b; } +f(1, { x: 1, y: 1 }); class C { m(b) { b; }