Skip to content

Commit 57feab3

Browse files
authored
Merge pull request #11546 from Microsoft/unusedidentifier
Codefix for removing Unused Identifiers
2 parents a74e9d8 + 1924298 commit 57feab3

File tree

87 files changed

+1154
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+1154
-3
lines changed

Diff for: src/harness/fourslash.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2013,13 +2013,13 @@ namespace FourSlash {
20132013
this.raiseError("Errors expected.");
20142014
}
20152015

2016-
if (diagnostics.length > 1 && errorCode !== undefined) {
2016+
if (diagnostics.length > 1 && errorCode === undefined) {
20172017
this.raiseError("When there's more than one error, you must specify the errror to fix.");
20182018
}
20192019

20202020
const diagnostic = !errorCode ? diagnostics[0] : ts.find(diagnostics, d => d.code == errorCode);
20212021

2022-
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.length, [diagnostic.code]);
2022+
return this.languageService.getCodeFixesAtPosition(fileName, diagnostic.start, diagnostic.start + diagnostic.length, [diagnostic.code]);
20232023
}
20242024

20252025
public verifyCodeFixAtPosition(expectedText: string, errorCode?: number) {

Diff for: src/services/codefixes/fixes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
///<reference path='superFixes.ts' />
2+
///<reference path='unusedIdentifierFixes.ts' />

Diff for: src/services/codefixes/unusedIdentifierFixes.ts

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
registerCodeFix({
4+
errorCodes: [
5+
Diagnostics._0_is_declared_but_never_used.code,
6+
Diagnostics.Property_0_is_declared_but_never_used.code
7+
],
8+
getCodeActions: (context: CodeFixContext) => {
9+
const sourceFile = context.sourceFile;
10+
const start = context.span.start;
11+
12+
let token = getTokenAtPosition(sourceFile, start);
13+
14+
// this handles var ["computed"] = 12;
15+
if (token.kind === SyntaxKind.OpenBracketToken) {
16+
token = getTokenAtPosition(sourceFile, start + 1);
17+
}
18+
19+
switch (token.kind) {
20+
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 createCodeFix("", forInitializer.pos, forInitializer.end - forInitializer.pos);
29+
}
30+
else {
31+
return removeSingleItem(forInitializer.declarations, token);
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 createCodeFix("{}", forOfInitializer.declarations[0].pos, forOfInitializer.declarations[0].end - forOfInitializer.declarations[0].pos);
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 createCodeFix("", parameter.pos, parameter.end - parameter.pos);
51+
52+
default:
53+
const variableStatement = <VariableStatement>token.parent.parent.parent;
54+
if (variableStatement.declarationList.declarations.length === 1) {
55+
return createCodeFix("", variableStatement.pos, variableStatement.end - variableStatement.pos);
56+
}
57+
else {
58+
const declarations = variableStatement.declarationList.declarations;
59+
return removeSingleItem(declarations, token);
60+
}
61+
}
62+
63+
case SyntaxKind.TypeParameter:
64+
const typeParameters = (<DeclarationWithTypeParameters>token.parent.parent).typeParameters;
65+
if (typeParameters.length === 1) {
66+
return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 2);
67+
}
68+
else {
69+
return removeSingleItem(typeParameters, token);
70+
}
71+
72+
case ts.SyntaxKind.Parameter:
73+
const functionDeclaration = <FunctionDeclaration>token.parent.parent;
74+
if (functionDeclaration.parameters.length === 1) {
75+
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos);
76+
}
77+
else {
78+
return removeSingleItem(functionDeclaration.parameters, token);
79+
}
80+
81+
// handle case where 'import a = A;'
82+
case SyntaxKind.ImportEqualsDeclaration:
83+
const importEquals = findImportDeclaration(token);
84+
return createCodeFix("", importEquals.pos, importEquals.end - importEquals.pos);
85+
86+
case SyntaxKind.ImportSpecifier:
87+
const namedImports = <NamedImports>token.parent.parent;
88+
if (namedImports.elements.length === 1) {
89+
// Only 1 import and it is unused. So the entire declaration should be removed.
90+
const importSpec = findImportDeclaration(token);
91+
return createCodeFix("", importSpec.pos, importSpec.end - importSpec.pos);
92+
}
93+
else {
94+
return removeSingleItem(namedImports.elements, token);
95+
}
96+
97+
// handle case where "import d, * as ns from './file'"
98+
// or "'import {a, b as ns} from './file'"
99+
case SyntaxKind.ImportClause: // this covers both 'import |d|' and 'import |d,| *'
100+
const importClause = <ImportClause>token.parent;
101+
if (!importClause.namedBindings) { // |import d from './file'| or |import * as ns from './file'|
102+
const importDecl = findImportDeclaration(importClause);
103+
return createCodeFix("", importDecl.pos, importDecl.end - importDecl.pos);
104+
}
105+
else { // import |d,| * as ns from './file'
106+
return createCodeFix("", importClause.name.pos, importClause.namedBindings.pos - importClause.name.pos);
107+
}
108+
109+
case SyntaxKind.NamespaceImport:
110+
const namespaceImport = <NamespaceImport>token.parent;
111+
if (namespaceImport.name == token && !(<ImportClause>namespaceImport.parent).name) {
112+
const importDecl = findImportDeclaration(namespaceImport);
113+
return createCodeFix("", importDecl.pos, importDecl.end - importDecl.pos);
114+
}
115+
else {
116+
const start = (<ImportClause>namespaceImport.parent).name.end;
117+
return createCodeFix("", start, (<ImportClause>namespaceImport.parent).namedBindings.end - start);
118+
}
119+
}
120+
break;
121+
122+
case SyntaxKind.PropertyDeclaration:
123+
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos);
124+
125+
case SyntaxKind.NamespaceImport:
126+
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos);
127+
}
128+
if (isDeclarationName(token)) {
129+
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos);
130+
}
131+
else if (isLiteralComputedPropertyDeclarationName(token)) {
132+
return createCodeFix("", token.parent.parent.pos, token.parent.parent.end - token.parent.parent.pos);
133+
}
134+
else {
135+
return undefined;
136+
}
137+
138+
function findImportDeclaration(token: Node): Node {
139+
let importDecl = token;
140+
while (importDecl.kind != SyntaxKind.ImportDeclaration && importDecl.parent) {
141+
importDecl = importDecl.parent;
142+
}
143+
144+
return importDecl;
145+
}
146+
147+
function createCodeFix(newText: string, start: number, length: number): CodeAction[] {
148+
return [{
149+
description: getLocaleSpecificMessage(Diagnostics.Remove_unused_identifiers),
150+
changes: [{
151+
fileName: sourceFile.fileName,
152+
textChanges: [{ newText, span: { start, length } }]
153+
}]
154+
}];
155+
}
156+
157+
function removeSingleItem<T extends Node>(elements: NodeArray<T>, token: T): CodeAction[] {
158+
if (elements[0] === token.parent) {
159+
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos + 1);
160+
}
161+
else {
162+
return createCodeFix("", token.parent.pos - 1, token.parent.end - token.parent.pos + 1);
163+
}
164+
}
165+
}
166+
});
167+
}

Diff for: src/services/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"compilerOptions": {
33
"noImplicitAny": true,
44
"noImplicitThis": true,

Diff for: tests/cases/fourslash/unusedClassInNamespace1.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| namespace greeter {
5+
//// class class1 {
6+
//// }
7+
//// } |]
8+
9+
verify.codeFixAtPosition(`namespace greeter {
10+
}`);

Diff for: tests/cases/fourslash/unusedClassInNamespace2.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| namespace greeter {
5+
//// export class class2 {
6+
//// }
7+
//// class class1 {
8+
//// }
9+
//// } |]
10+
11+
verify.codeFixAtPosition(`namespace greeter {
12+
export class class2 {
13+
}
14+
}`);
15+

Diff for: tests/cases/fourslash/unusedClassInNamespace3.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters:true
5+
//// [| namespace Validation {
6+
//// class c1 {
7+
////
8+
//// }
9+
////
10+
//// export class c2 {
11+
////
12+
//// }
13+
////
14+
//// class c3 extends c1 {
15+
////
16+
//// }
17+
////} |]
18+
19+
verify.codeFixAtPosition(`namespace Validation {
20+
class c1 {
21+
}
22+
23+
export class c2 {
24+
}
25+
}`);

Diff for: tests/cases/fourslash/unusedClassInNamespace4.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters:true
5+
//// [| namespace Validation {
6+
//// class c1 {
7+
////
8+
//// }
9+
////
10+
//// export class c2 {
11+
////
12+
//// }
13+
////
14+
//// class c3 {
15+
//// public x: c1;
16+
//// }
17+
////} |]
18+
19+
verify.codeFixAtPosition(`namespace Validation {
20+
class c1 {
21+
22+
}
23+
24+
export class c2 {
25+
26+
}
27+
}`);

Diff for: tests/cases/fourslash/unusedConstantInFunction1.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| function f1 () {
5+
//// const x: string = "x";
6+
//// } |]
7+
8+
verify.codeFixAtPosition(`function f1 () {
9+
}`);
10+

Diff for: tests/cases/fourslash/unusedEnumInFunction1.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| function f1 () {
5+
//// enum Directions { Up, Down}
6+
//// } |]
7+
8+
verify.codeFixAtPosition(`function f1 () {
9+
}
10+
`);
11+

Diff for: tests/cases/fourslash/unusedEnumInNamespace1.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| namespace greeter {
5+
//// enum enum1 {
6+
//// Monday
7+
//// }
8+
//// } |]
9+
10+
verify.codeFixAtPosition(`namespace greeter {
11+
}`);

Diff for: tests/cases/fourslash/unusedFunctionInNamespace1.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| namespace greeter {
5+
//// function function1() {
6+
//// }/*1*/
7+
//// } |]
8+
9+
verify.codeFixAtPosition(`namespace greeter {
10+
}`);

Diff for: tests/cases/fourslash/unusedFunctionInNamespace2.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
//// [| namespace greeter {
5+
//// export function function2() {
6+
//// }
7+
//// function function1() {
8+
//// }
9+
////} |]
10+
11+
verify.codeFixAtPosition(`namespace greeter {
12+
export function function2() {
13+
}
14+
}`);

Diff for: tests/cases/fourslash/unusedFunctionInNamespace3.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters:true
5+
6+
//// [| namespace Validation {
7+
//// function function1() {
8+
//// }
9+
////} |]
10+
11+
verify.codeFixAtPosition(`namespace Validation {
12+
}`);

Diff for: tests/cases/fourslash/unusedFunctionInNamespace4.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters:true
5+
//// [| namespace Validation {
6+
//// var function1 = function() {
7+
//// }
8+
////} |]
9+
10+
verify.codeFixAtPosition(`namespace Validation {
11+
}`);

Diff for: tests/cases/fourslash/unusedFunctionInNamespace5.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noUnusedLocals: true
4+
// @noUnusedParameters:true
5+
////namespace Validation {
6+
//// var function1 = function() {
7+
//// }
8+
////
9+
//// export function function2() {
10+
////
11+
//// }
12+
////
13+
//// [| function function3() {
14+
//// function1();
15+
//// }
16+
////
17+
//// function function4() {
18+
////
19+
//// }
20+
////
21+
//// export let a = function3; |]
22+
////}
23+
24+
verify.codeFixAtPosition(`function function3() {
25+
function1();
26+
}
27+
28+
export let a = function3;`);

0 commit comments

Comments
 (0)