diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 70efe50143e..fda9cd8a153 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3621,6 +3621,10 @@ "category": "Message", "code": 90024 }, + "Prefix '{0}' with an underscore.": { + "category": "Message", + "code": 90025 + }, "Convert function to an ES2015 class": { "category": "Message", diff --git a/src/services/codefixes/fixUnusedIdentifier.ts b/src/services/codefixes/fixUnusedIdentifier.ts index 5d85979fcf9..c3e7e2bb4f2 100644 --- a/src/services/codefixes/fixUnusedIdentifier.ts +++ b/src/services/codefixes/fixUnusedIdentifier.ts @@ -18,7 +18,9 @@ namespace ts.codefix { switch (token.kind) { case ts.SyntaxKind.Identifier: - return deleteIdentifier(); + let actions = deleteIdentifier(token); + (actions || (actions = [])).push(prefixIdentifierWithUnderscore(token)); + return actions; case SyntaxKind.PropertyDeclaration: case SyntaxKind.NamespaceImport: @@ -40,58 +42,68 @@ namespace ts.codefix { } } - function deleteIdentifier(): CodeAction[] | undefined { - switch (token.parent.kind) { + function prefixIdentifierWithUnderscore(identifier: Identifier): CodeAction { + // TODO: make sure this work with prefixing trivia. + const startPosition = identifier.getStart(sourceFile, /*includeJsDocComment*/ false); + return { + description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Prefix_0_with_an_underscore), { 0: token.getText() }), + changes: [{ + fileName: sourceFile.path, + textChanges: [{ + span: { start: startPosition, length: 0 }, + newText: "_" + }] + }] + }; + } + + function deleteIdentifier(identifier: Identifier): CodeAction[] | undefined { + const parent = identifier.parent; + switch (parent.kind) { case ts.SyntaxKind.VariableDeclaration: - return deleteVariableDeclaration(token.parent); + return deleteVariableDeclaration(parent); case SyntaxKind.TypeParameter: - const typeParameters = (token.parent.parent).typeParameters; + const typeParameters = (parent.parent).typeParameters; if (typeParameters.length === 1) { const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1, /*includeJsDocComment*/ false); - if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) { - return deleteRange(typeParameters); - } const nextToken = getTokenAtPosition(sourceFile, typeParameters.end, /*includeJsDocComment*/ false); - if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) { - return deleteRange(typeParameters); - } + Debug.assert(previousToken.kind === SyntaxKind.LessThanToken); + Debug.assert(nextToken.kind === SyntaxKind.GreaterThanToken); + return deleteNodeRange(previousToken, nextToken); } else { - return deleteNodeInList(token.parent); + return deleteNodeInList(parent); } case ts.SyntaxKind.Parameter: - const functionDeclaration = token.parent.parent; - if (functionDeclaration.parameters.length === 1) { - return deleteNode(token.parent); - } - else { - return deleteNodeInList(token.parent); - } + const functionDeclaration = parent.parent; + return functionDeclaration.parameters.length === 1 ? + deleteNode(parent) : + deleteNodeInList(parent); // handle case where 'import a = A;' case SyntaxKind.ImportEqualsDeclaration: - const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration); + const importEquals = getAncestor(identifier, SyntaxKind.ImportEqualsDeclaration); return deleteNode(importEquals); case SyntaxKind.ImportSpecifier: - const namedImports = token.parent.parent; + const namedImports = parent.parent; if (namedImports.elements.length === 1) { // Only 1 import and it is unused. So the entire declaration should be removed. - const importSpec = getAncestor(token, SyntaxKind.ImportDeclaration); + const importSpec = getAncestor(identifier, SyntaxKind.ImportDeclaration); return deleteNode(importSpec); } else { // delete import specifier - return deleteNodeInList(token.parent); + return deleteNodeInList(parent); } // handle case where "import d, * as ns from './file'" // or "'import {a, b as ns} from './file'" case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *' - const importClause = token.parent; + const importClause = parent; if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'| const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); return deleteNode(importDecl); @@ -110,8 +122,8 @@ namespace ts.codefix { } case SyntaxKind.NamespaceImport: - const namespaceImport = token.parent; - if (namespaceImport.name === token && !(namespaceImport.parent).name) { + const namespaceImport = parent; + if (namespaceImport.name === identifier && !(namespaceImport.parent).name) { const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration); return deleteNode(importDecl); }