-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Codefix for removing Unused Identifiers #11546
Conversation
const start = context.span.start; | ||
const token = getTokenAtPosition(sourceFile, start); | ||
|
||
if (token.kind === ts.SyntaxKind.Identifier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please refactor this into a switch statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
return createCodeFix("{}", token.parent.pos, token.parent.end - token.parent.pos); | ||
} | ||
|
||
if (token.parent.kind === SyntaxKind.ImportEqualsDeclaration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does this mean. this is import m = require("mod");
, you want to remove the whole declaration, and not just the identifier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, please add a test for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is import a = A
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests are here, changed this to remove the whole line.
} | ||
|
||
if (token.kind === SyntaxKind.AsteriskToken && token.parent.kind === SyntaxKind.NamespaceImport) { | ||
return createCodeFix("{}", token.parent.pos, token.parent.end - token.parent.pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not think this is correct either. you need to check if this is the only import for this module, remove it whole sale, if not, remove whole import declaration.
also please add tests for the deffrent combinations of import:
import * as ns from "mod";
import d from "mod"
import {a, b} from "mod" // where both are unused
import {a, b} from "mod" // where only one is unused
import d, * as ns from "mod"; // where both are unused
import d, * as ns from "mod"; // where one of them is unsued, but the other is
import d, {a, b} from "mod"; // where one of a,b, or d is unused, but other are not
import d, {a, b} from "mod"; // where both a and b are unused, but d is not
import d, {a, b} from "mod"; // where only d is unsued
import d, {a, b} from "mod"; // where all are unused
import m = require("mod");
import m = n.a;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the cases where more than 1 import are unused, will result in 2 errors, and 2 fixes; so those would be identical to the single unused import.
All other cases have test cases.
//// [|constructor(private p1: string, public p2: boolean, public p3: any, p5)|] { p5; } | ||
//// } | ||
|
||
verify.codeFixAtPosition("constructor(public p2: boolean, public p3: any, p5)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add another test with private readonly
and another one with private
is not in the first position.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added
const elements = namedImports.elements; | ||
if (elements.length === 1) { | ||
// Only 1 import and it is unused. So the entire line could be removed. | ||
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not removing the whole line? do you mean namedImports.parent? I think you need to handle all cases. i have listed them below, also please add tests for each combination.
} | ||
|
||
if (token.parent.kind === SyntaxKind.TypeParameter) { | ||
const typeParameters = (<ClassDeclaration>token.parent.parent).typeParameters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why ClassDeclaraton? that can be function, interface declaration, method, type alias, arrow function, etc.. please add tests for these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used DeclWIthTypeParam, tests were already there.
} | ||
|
||
if (token.parent.kind === ts.SyntaxKind.Parameter) { | ||
const functionDeclaration = <FunctionDeclaration>token.parent.parent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same logic as above, please merge these two branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
token.parent.kind === SyntaxKind.MethodDeclaration || | ||
token.parent.kind === SyntaxKind.ModuleDeclaration || | ||
token.parent.kind === SyntaxKind.PropertyDeclaration || | ||
token.parent.kind === SyntaxKind.ArrowFunction) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can an arrowFunction have an identifier as a name?
consider changing this to:
if (isDeclaration(token.parent) && (<Declaration>parent).name === token) {
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please add handeling for computed properties with non-identifier names:
class C {
private ["string"] (){}
private [Symbol.Iterator]() {}
}
else if (token.parent.parent.parent.kind === SyntaxKind.ForInStatement) { | ||
const forInStatement = <ForInStatement>token.parent.parent.parent; | ||
const initializer = <VariableDeclarationList>forInStatement.initializer; | ||
return createCodeFix("{}", initializer.declarations[0].pos, initializer.declarations[0].end - initializer.declarations[0].pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not valid for..in
statement. we should switch it x
to _x
and add special casing for _x
to be not show the error. alternatively do nothing here, which would be my recommendation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing nothing, modified test to make sure.
} | ||
else { | ||
const variableStatement = <VariableStatement>token.parent.parent.parent; | ||
if (variableStatement.declarationList.declarations.length === 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider sharing the logic here with the other list removal cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
if (token.parent.parent.parent.kind === SyntaxKind.ForStatement) { | ||
const forStatement = <ForStatement>token.parent.parent.parent; | ||
const initializer = <VariableDeclarationList>forStatement.initializer; | ||
if (initializer.declarations.length === 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider merging this into a list removal logic for all of the cases below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about binding patterns? can you add tests for these cases. both for parameters and variable declarations.
token.parent.kind === SyntaxKind.MethodDeclaration || | ||
token.parent.kind === SyntaxKind.ModuleDeclaration || | ||
token.parent.kind === SyntaxKind.PropertyDeclaration || | ||
token.parent.kind === SyntaxKind.ArrowFunction) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please add handeling for computed properties with non-identifier names:
class C {
private ["string"] (){}
private [Symbol.Iterator]() {}
}
//// x++; | ||
////} | ||
|
||
verify.codeFixAtPosition("var x;", 6133); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No error codes in fourslash tests
break; | ||
|
||
case SyntaxKind.ForInStatement: | ||
// There is no valid fix in the case of: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename the variable to start with _
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of scope, will add for 2nd iteration of this fix
} | ||
} | ||
|
||
case SyntaxKind.FunctionDeclaration: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
namespace, type alais, and enum as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would move this to the default clause and use isDeclarationName
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
case SyntaxKind.ImportSpecifier: | ||
const namedImports = <NamedImports>token.parent.parent; | ||
const elements = namedImports.elements; | ||
if (elements.length === 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if no other imports in this list we need to remove the whole module import.
} | ||
break; | ||
|
||
case SyntaxKind.PrivateKeyword: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is this ever used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
const token = getTokenAtPosition(sourceFile, start); | ||
|
||
switch (token.kind) { | ||
case ts.SyntaxKind.Identifier: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
binding patterns are not handled anywhere:
function f({a, b}) {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outside of scope, I think we can do a better job when we add the change signature refactoring
const token = getTokenAtPosition(sourceFile, start); | ||
|
||
switch (token.kind) { | ||
case ts.SyntaxKind.Identifier: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also please add handeling for computed properties with non-identifier names:
class C {
private ["string"] (){}
private [Symbol.Iterator]() {}
}
|
||
// @noUnusedLocals: true | ||
// @Filename: file2.ts | ||
//// [| import f1, * as s from "./file1"; |] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test with both unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That generates 2 errors and 2 fixes. So not applicable
case SyntaxKind.PropertyDeclaration: | ||
return createCodeFix("", token.parent.pos, token.parent.end - token.parent.pos); | ||
|
||
case SyntaxKind.AsteriskToken: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when do we get a *
token?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
Quick fix for removing unused variables and unused locals.
Current known limitations:
for ... in
loop