From 3590048d9b3e9880ead57bfd21bfd7a6f9ae7c18 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 1 May 2017 08:52:34 -0700 Subject: [PATCH] unusedIdentifierFixes: Factor out nested switch statements and fix implicit fallthrough --- .../codefixes/unusedIdentifierFixes.ts | 266 +++++++++--------- 1 file changed, 137 insertions(+), 129 deletions(-) diff --git a/src/services/codefixes/unusedIdentifierFixes.ts b/src/services/codefixes/unusedIdentifierFixes.ts index e1debfa4ffb..422a28098a6 100644 --- a/src/services/codefixes/unusedIdentifierFixes.ts +++ b/src/services/codefixes/unusedIdentifierFixes.ts @@ -18,142 +18,150 @@ namespace ts.codefix { switch (token.kind) { case ts.SyntaxKind.Identifier: - switch (token.parent.kind) { - case ts.SyntaxKind.VariableDeclaration: - switch (token.parent.parent.parent.kind) { - case SyntaxKind.ForStatement: - const forStatement = token.parent.parent.parent; - const forInitializer = forStatement.initializer; - if (forInitializer.declarations.length === 1) { - return deleteNode(forInitializer); - } - else { - return deleteNodeInList(token.parent); - } - - case SyntaxKind.ForOfStatement: - const forOfStatement = token.parent.parent.parent; - if (forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) { - const forOfInitializer = forOfStatement.initializer; - return replaceNode(forOfInitializer.declarations[0], createObjectLiteral()); - } - break; - - case SyntaxKind.ForInStatement: - // There is no valid fix in the case of: - // for .. in - return undefined; - - case SyntaxKind.CatchClause: - const catchClause = token.parent.parent; - const parameter = catchClause.variableDeclaration.getChildren()[0]; - return deleteNode(parameter); - - default: - const variableStatement = token.parent.parent.parent; - if (variableStatement.declarationList.declarations.length === 1) { - return deleteNode(variableStatement); - } - else { - return deleteNodeInList(token.parent); - } - } - // TODO: #14885 - // falls through - - case SyntaxKind.TypeParameter: - const typeParameters = (token.parent.parent).typeParameters; - if (typeParameters.length === 1) { - const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1); - if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) { - return deleteRange(typeParameters); - } - const nextToken = getTokenAtPosition(sourceFile, typeParameters.end); - if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) { - return deleteRange(typeParameters); - } - return deleteNodeRange(previousToken, nextToken); - } - else { - return deleteNodeInList(token.parent); - } - - case ts.SyntaxKind.Parameter: - const functionDeclaration = token.parent.parent; - if (functionDeclaration.parameters.length === 1) { - return deleteNode(token.parent); - } - else { - return deleteNodeInList(token.parent); - } - - // handle case where 'import a = A;' - case SyntaxKind.ImportEqualsDeclaration: - const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration); - return deleteNode(importEquals); - - case SyntaxKind.ImportSpecifier: - const namedImports = token.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); - return deleteNode(importSpec); - } - else { - // delete import specifier - return deleteNodeInList(token.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; - if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'| - const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); - return deleteNode(importDecl); - } - else { - // import |d,| * as ns from './file' - const start = importClause.name.getStart(sourceFile); - const nextToken = getTokenAtPosition(sourceFile, importClause.name.end); - if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { - // shift first non-whitespace position after comma to the start position of the node - return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) }); - } - else { - return deleteNode(importClause.name); - } - } - - case SyntaxKind.NamespaceImport: - const namespaceImport = token.parent; - if (namespaceImport.name === token && !(namespaceImport.parent).name) { - const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration); - return deleteNode(importDecl); - } - else { - const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); - if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { - const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); - return deleteRange({ pos: startPosition, end: namespaceImport.end }); - } - return deleteRange(namespaceImport); - } - } - break; + return deleteIdentifier(); case SyntaxKind.PropertyDeclaration: case SyntaxKind.NamespaceImport: return deleteNode(token.parent); + + default: + return deleteDefault(); } - if (isDeclarationName(token)) { - return deleteNode(token.parent); + + function deleteDefault() { + if (isDeclarationName(token)) { + return deleteNode(token.parent); + } + else if (isLiteralComputedPropertyDeclarationName(token)) { + return deleteNode(token.parent.parent); + } + else { + return undefined; + } } - else if (isLiteralComputedPropertyDeclarationName(token)) { - return deleteNode(token.parent.parent); + + function deleteIdentifier(): CodeAction[] | undefined { + switch (token.parent.kind) { + case ts.SyntaxKind.VariableDeclaration: + return deleteVariableDeclaration(token.parent); + + case SyntaxKind.TypeParameter: + const typeParameters = (token.parent.parent).typeParameters; + if (typeParameters.length === 1) { + const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1); + if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) { + return deleteRange(typeParameters); + } + const nextToken = getTokenAtPosition(sourceFile, typeParameters.end); + if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) { + return deleteRange(typeParameters); + } + return deleteNodeRange(previousToken, nextToken); + } + else { + return deleteNodeInList(token.parent); + } + + case ts.SyntaxKind.Parameter: + const functionDeclaration = token.parent.parent; + if (functionDeclaration.parameters.length === 1) { + return deleteNode(token.parent); + } + else { + return deleteNodeInList(token.parent); + } + + // handle case where 'import a = A;' + case SyntaxKind.ImportEqualsDeclaration: + const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration); + return deleteNode(importEquals); + + case SyntaxKind.ImportSpecifier: + const namedImports = token.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); + return deleteNode(importSpec); + } + else { + // delete import specifier + return deleteNodeInList(token.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; + if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'| + const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration); + return deleteNode(importDecl); + } + else { + // import |d,| * as ns from './file' + const start = importClause.name.getStart(sourceFile); + const nextToken = getTokenAtPosition(sourceFile, importClause.name.end); + if (nextToken && nextToken.kind === SyntaxKind.CommaToken) { + // shift first non-whitespace position after comma to the start position of the node + return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) }); + } + else { + return deleteNode(importClause.name); + } + } + + case SyntaxKind.NamespaceImport: + const namespaceImport = token.parent; + if (namespaceImport.name === token && !(namespaceImport.parent).name) { + const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration); + return deleteNode(importDecl); + } + else { + const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1); + if (previousToken && previousToken.kind === SyntaxKind.CommaToken) { + const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart); + return deleteRange({ pos: startPosition, end: namespaceImport.end }); + } + return deleteRange(namespaceImport); + } + + default: + return deleteDefault(); + } } - else { - return undefined; + + // token.parent is a variableDeclaration + function deleteVariableDeclaration(varDecl: ts.VariableDeclaration): CodeAction[] | undefined { + switch (varDecl.parent.parent.kind) { + case SyntaxKind.ForStatement: + const forStatement = varDecl.parent.parent; + const forInitializer = forStatement.initializer; + if (forInitializer.declarations.length === 1) { + return deleteNode(forInitializer); + } + else { + return deleteNodeInList(varDecl); + } + + case SyntaxKind.ForOfStatement: + const forOfStatement = varDecl.parent.parent; + Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList); + const forOfInitializer = forOfStatement.initializer; + return replaceNode(forOfInitializer.declarations[0], createObjectLiteral()); + + case SyntaxKind.ForInStatement: + // There is no valid fix in the case of: + // for .. in + return undefined; + + default: + const variableStatement = varDecl.parent.parent; + if (variableStatement.declarationList.declarations.length === 1) { + return deleteNode(variableStatement); + } + else { + return deleteNodeInList(varDecl); + } + } } function deleteNode(n: Node) {