Skip to content

Commit 20bba9c

Browse files
author
Andy
authored
Merge pull request #15496 from Microsoft/rm_fallthrough
unusedIdentifierFixes: Factor out nested switch statements and fix implicit fallthrough
2 parents 5e7778b + 3590048 commit 20bba9c

File tree

1 file changed

+134
-126
lines changed

1 file changed

+134
-126
lines changed

src/services/codefixes/unusedIdentifierFixes.ts

+134-126
Original file line numberDiff line numberDiff line change
@@ -18,142 +18,150 @@ namespace ts.codefix {
1818

1919
switch (token.kind) {
2020
case ts.SyntaxKind.Identifier:
21-
switch (token.parent.kind) {
22-
case ts.SyntaxKind.VariableDeclaration:
23-
switch (token.parent.parent.parent.kind) {
24-
case SyntaxKind.ForStatement:
25-
const forStatement = <ForStatement>token.parent.parent.parent;
26-
const forInitializer = <VariableDeclarationList>forStatement.initializer;
27-
if (forInitializer.declarations.length === 1) {
28-
return deleteNode(forInitializer);
29-
}
30-
else {
31-
return deleteNodeInList(token.parent);
32-
}
33-
34-
case SyntaxKind.ForOfStatement:
35-
const forOfStatement = <ForOfStatement>token.parent.parent.parent;
36-
if (forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList) {
37-
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
38-
return replaceNode(forOfInitializer.declarations[0], createObjectLiteral());
39-
}
40-
break;
41-
42-
case SyntaxKind.ForInStatement:
43-
// There is no valid fix in the case of:
44-
// for .. in
45-
return undefined;
46-
47-
case SyntaxKind.CatchClause:
48-
const catchClause = <CatchClause>token.parent.parent;
49-
const parameter = catchClause.variableDeclaration.getChildren()[0];
50-
return deleteNode(parameter);
51-
52-
default:
53-
const variableStatement = <VariableStatement>token.parent.parent.parent;
54-
if (variableStatement.declarationList.declarations.length === 1) {
55-
return deleteNode(variableStatement);
56-
}
57-
else {
58-
return deleteNodeInList(token.parent);
59-
}
60-
}
61-
// TODO: #14885
62-
// falls through
63-
64-
case SyntaxKind.TypeParameter:
65-
const typeParameters = (<DeclarationWithTypeParameters>token.parent.parent).typeParameters;
66-
if (typeParameters.length === 1) {
67-
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1);
68-
if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) {
69-
return deleteRange(typeParameters);
70-
}
71-
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end);
72-
if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) {
73-
return deleteRange(typeParameters);
74-
}
75-
return deleteNodeRange(previousToken, nextToken);
76-
}
77-
else {
78-
return deleteNodeInList(token.parent);
79-
}
21+
return deleteIdentifier();
8022

81-
case ts.SyntaxKind.Parameter:
82-
const functionDeclaration = <FunctionDeclaration>token.parent.parent;
83-
if (functionDeclaration.parameters.length === 1) {
84-
return deleteNode(token.parent);
85-
}
86-
else {
87-
return deleteNodeInList(token.parent);
88-
}
23+
case SyntaxKind.PropertyDeclaration:
24+
case SyntaxKind.NamespaceImport:
25+
return deleteNode(token.parent);
8926

90-
// handle case where 'import a = A;'
91-
case SyntaxKind.ImportEqualsDeclaration:
92-
const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration);
93-
return deleteNode(importEquals);
94-
95-
case SyntaxKind.ImportSpecifier:
96-
const namedImports = <NamedImports>token.parent.parent;
97-
if (namedImports.elements.length === 1) {
98-
// Only 1 import and it is unused. So the entire declaration should be removed.
99-
const importSpec = getAncestor(token, SyntaxKind.ImportDeclaration);
100-
return deleteNode(importSpec);
101-
}
102-
else {
103-
// delete import specifier
104-
return deleteNodeInList(token.parent);
105-
}
27+
default:
28+
return deleteDefault();
29+
}
30+
31+
function deleteDefault() {
32+
if (isDeclarationName(token)) {
33+
return deleteNode(token.parent);
34+
}
35+
else if (isLiteralComputedPropertyDeclarationName(token)) {
36+
return deleteNode(token.parent.parent);
37+
}
38+
else {
39+
return undefined;
40+
}
41+
}
10642

107-
// handle case where "import d, * as ns from './file'"
108-
// or "'import {a, b as ns} from './file'"
109-
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
110-
const importClause = <ImportClause>token.parent;
111-
if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'|
112-
const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration);
113-
return deleteNode(importDecl);
43+
function deleteIdentifier(): CodeAction[] | undefined {
44+
switch (token.parent.kind) {
45+
case ts.SyntaxKind.VariableDeclaration:
46+
return deleteVariableDeclaration(<ts.VariableDeclaration>token.parent);
47+
48+
case SyntaxKind.TypeParameter:
49+
const typeParameters = (<DeclarationWithTypeParameters>token.parent.parent).typeParameters;
50+
if (typeParameters.length === 1) {
51+
const previousToken = getTokenAtPosition(sourceFile, typeParameters.pos - 1);
52+
if (!previousToken || previousToken.kind !== SyntaxKind.LessThanToken) {
53+
return deleteRange(typeParameters);
11454
}
115-
else {
116-
// import |d,| * as ns from './file'
117-
const start = importClause.name.getStart(sourceFile);
118-
const nextToken = getTokenAtPosition(sourceFile, importClause.name.end);
119-
if (nextToken && nextToken.kind === SyntaxKind.CommaToken) {
120-
// shift first non-whitespace position after comma to the start position of the node
121-
return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) });
122-
}
123-
else {
124-
return deleteNode(importClause.name);
125-
}
55+
const nextToken = getTokenAtPosition(sourceFile, typeParameters.end);
56+
if (!nextToken || nextToken.kind !== SyntaxKind.GreaterThanToken) {
57+
return deleteRange(typeParameters);
12658
}
127-
128-
case SyntaxKind.NamespaceImport:
129-
const namespaceImport = <NamespaceImport>token.parent;
130-
if (namespaceImport.name === token && !(<ImportClause>namespaceImport.parent).name) {
131-
const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration);
132-
return deleteNode(importDecl);
59+
return deleteNodeRange(previousToken, nextToken);
60+
}
61+
else {
62+
return deleteNodeInList(token.parent);
63+
}
64+
65+
case ts.SyntaxKind.Parameter:
66+
const functionDeclaration = <FunctionDeclaration>token.parent.parent;
67+
if (functionDeclaration.parameters.length === 1) {
68+
return deleteNode(token.parent);
69+
}
70+
else {
71+
return deleteNodeInList(token.parent);
72+
}
73+
74+
// handle case where 'import a = A;'
75+
case SyntaxKind.ImportEqualsDeclaration:
76+
const importEquals = getAncestor(token, SyntaxKind.ImportEqualsDeclaration);
77+
return deleteNode(importEquals);
78+
79+
case SyntaxKind.ImportSpecifier:
80+
const namedImports = <NamedImports>token.parent.parent;
81+
if (namedImports.elements.length === 1) {
82+
// Only 1 import and it is unused. So the entire declaration should be removed.
83+
const importSpec = getAncestor(token, SyntaxKind.ImportDeclaration);
84+
return deleteNode(importSpec);
85+
}
86+
else {
87+
// delete import specifier
88+
return deleteNodeInList(token.parent);
89+
}
90+
91+
// handle case where "import d, * as ns from './file'"
92+
// or "'import {a, b as ns} from './file'"
93+
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
94+
const importClause = <ImportClause>token.parent;
95+
if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'|
96+
const importDecl = getAncestor(importClause, SyntaxKind.ImportDeclaration);
97+
return deleteNode(importDecl);
98+
}
99+
else {
100+
// import |d,| * as ns from './file'
101+
const start = importClause.name.getStart(sourceFile);
102+
const nextToken = getTokenAtPosition(sourceFile, importClause.name.end);
103+
if (nextToken && nextToken.kind === SyntaxKind.CommaToken) {
104+
// shift first non-whitespace position after comma to the start position of the node
105+
return deleteRange({ pos: start, end: skipTrivia(sourceFile.text, nextToken.end, /*stopAfterLineBreaks*/ false, /*stopAtComments*/ true) });
133106
}
134107
else {
135-
const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1);
136-
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
137-
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart);
138-
return deleteRange({ pos: startPosition, end: namespaceImport.end });
139-
}
140-
return deleteRange(namespaceImport);
108+
return deleteNode(importClause.name);
109+
}
110+
}
111+
112+
case SyntaxKind.NamespaceImport:
113+
const namespaceImport = <NamespaceImport>token.parent;
114+
if (namespaceImport.name === token && !(<ImportClause>namespaceImport.parent).name) {
115+
const importDecl = getAncestor(namespaceImport, SyntaxKind.ImportDeclaration);
116+
return deleteNode(importDecl);
117+
}
118+
else {
119+
const previousToken = getTokenAtPosition(sourceFile, namespaceImport.pos - 1);
120+
if (previousToken && previousToken.kind === SyntaxKind.CommaToken) {
121+
const startPosition = textChanges.getAdjustedStartPosition(sourceFile, previousToken, {}, textChanges.Position.FullStart);
122+
return deleteRange({ pos: startPosition, end: namespaceImport.end });
141123
}
142-
}
143-
break;
124+
return deleteRange(namespaceImport);
125+
}
144126

145-
case SyntaxKind.PropertyDeclaration:
146-
case SyntaxKind.NamespaceImport:
147-
return deleteNode(token.parent);
127+
default:
128+
return deleteDefault();
129+
}
148130
}
149-
if (isDeclarationName(token)) {
150-
return deleteNode(token.parent);
151-
}
152-
else if (isLiteralComputedPropertyDeclarationName(token)) {
153-
return deleteNode(token.parent.parent);
154-
}
155-
else {
156-
return undefined;
131+
132+
// token.parent is a variableDeclaration
133+
function deleteVariableDeclaration(varDecl: ts.VariableDeclaration): CodeAction[] | undefined {
134+
switch (varDecl.parent.parent.kind) {
135+
case SyntaxKind.ForStatement:
136+
const forStatement = <ForStatement>varDecl.parent.parent;
137+
const forInitializer = <VariableDeclarationList>forStatement.initializer;
138+
if (forInitializer.declarations.length === 1) {
139+
return deleteNode(forInitializer);
140+
}
141+
else {
142+
return deleteNodeInList(varDecl);
143+
}
144+
145+
case SyntaxKind.ForOfStatement:
146+
const forOfStatement = <ForOfStatement>varDecl.parent.parent;
147+
Debug.assert(forOfStatement.initializer.kind === SyntaxKind.VariableDeclarationList);
148+
const forOfInitializer = <VariableDeclarationList>forOfStatement.initializer;
149+
return replaceNode(forOfInitializer.declarations[0], createObjectLiteral());
150+
151+
case SyntaxKind.ForInStatement:
152+
// There is no valid fix in the case of:
153+
// for .. in
154+
return undefined;
155+
156+
default:
157+
const variableStatement = <VariableStatement>varDecl.parent.parent;
158+
if (variableStatement.declarationList.declarations.length === 1) {
159+
return deleteNode(variableStatement);
160+
}
161+
else {
162+
return deleteNodeInList(varDecl);
163+
}
164+
}
157165
}
158166

159167
function deleteNode(n: Node) {

0 commit comments

Comments
 (0)