Skip to content

unusedIdentifierFixes: Factor out nested switch statements and fix implicit fallthrough #15496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
1 commit merged into from
May 1, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 134 additions & 126 deletions src/services/codefixes/unusedIdentifierFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <ForStatement>token.parent.parent.parent;
const forInitializer = <VariableDeclarationList>forStatement.initializer;
if (forInitializer.declarations.length === 1) {
return deleteNode(forInitializer);
}
else {
return deleteNodeInList(token.parent);
}

case SyntaxKind.ForOfStatement:
const forOfStatement = <ForOfStatement>token.parent.parent.parent;
if (forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) {
const forOfInitializer = <VariableDeclarationList>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 = <CatchClause>token.parent.parent;
const parameter = catchClause.variableDeclaration.getChildren()[0];
return deleteNode(parameter);

default:
const variableStatement = <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 = (<DeclarationWithTypeParameters>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);
}
return deleteIdentifier();

case ts.SyntaxKind.Parameter:
const functionDeclaration = <FunctionDeclaration>token.parent.parent;
if (functionDeclaration.parameters.length === 1) {
return deleteNode(token.parent);
}
else {
return deleteNodeInList(token.parent);
}
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.NamespaceImport:
return deleteNode(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 = <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);
}
default:
return deleteDefault();
}

function deleteDefault() {
if (isDeclarationName(token)) {
return deleteNode(token.parent);
}
else if (isLiteralComputedPropertyDeclarationName(token)) {
return deleteNode(token.parent.parent);
}
else {
return undefined;
}
}

// 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 = <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);
function deleteIdentifier(): CodeAction[] | undefined {
switch (token.parent.kind) {
case ts.SyntaxKind.VariableDeclaration:
return deleteVariableDeclaration(<ts.VariableDeclaration>token.parent);

case SyntaxKind.TypeParameter:
const typeParameters = (<DeclarationWithTypeParameters>token.parent.parent).typeParameters;
if (typeParameters.length === 1) {
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1);
if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) {
return deleteRange(typeParameters);
}
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);
}
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end);
if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) {
return deleteRange(typeParameters);
}

case SyntaxKind.NamespaceImport:
const namespaceImport = <NamespaceImport>token.parent;
if (namespaceImport.name === token && !(<ImportClause>namespaceImport.parent).name) {
const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration);
return deleteNode(importDecl);
return deleteNodeRange(previousToken, nextToken);
}
else {
return deleteNodeInList(token.parent);
}

case ts.SyntaxKind.Parameter:
const functionDeclaration = <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 = <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 = <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 {
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);
return deleteNode(importClause.name);
}
}

case SyntaxKind.NamespaceImport:
const namespaceImport = <NamespaceImport>token.parent;
if (namespaceImport.name === token && !(<ImportClause>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 });
}
}
break;
return deleteRange(namespaceImport);
}

case SyntaxKind.PropertyDeclaration:
case SyntaxKind.NamespaceImport:
return deleteNode(token.parent);
default:
return deleteDefault();
}
}
if (isDeclarationName(token)) {
return deleteNode(token.parent);
}
else if (isLiteralComputedPropertyDeclarationName(token)) {
return deleteNode(token.parent.parent);
}
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 = <ForStatement>varDecl.parent.parent;
const forInitializer = <VariableDeclarationList>forStatement.initializer;
if (forInitializer.declarations.length === 1) {
return deleteNode(forInitializer);
}
else {
return deleteNodeInList(varDecl);
}

case SyntaxKind.ForOfStatement:
const forOfStatement = <ForOfStatement>varDecl.parent.parent;
Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList);
const forOfInitializer = <VariableDeclarationList>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 = <VariableStatement>varDecl.parent.parent;
if (variableStatement.declarationList.declarations.length === 1) {
return deleteNode(variableStatement);
}
else {
return deleteNodeInList(varDecl);
}
}
}

function deleteNode(n: Node) {
Expand Down