Skip to content
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

Merged
merged 13 commits into from
Nov 15, 2016
Merged

Codefix for removing Unused Identifiers #11546

merged 13 commits into from
Nov 15, 2016

Conversation

paulvanbrenk
Copy link
Contributor

@paulvanbrenk paulvanbrenk commented Oct 12, 2016

Quick fix for removing unused variables and unused locals.

Current known limitations:

  • no support for the spread operator
  • no support for the for ... in loop
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);

if (token.kind === ts.SyntaxKind.Identifier) {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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);
Copy link
Contributor

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;
Copy link
Contributor Author

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)");
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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);
}
Copy link
Contributor

@mhegazy mhegazy Oct 13, 2016

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

@paulvanbrenk paulvanbrenk Oct 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
}
}
}
Copy link
Contributor

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) {
Copy link
Contributor

@mhegazy mhegazy Oct 13, 2016

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);
Copy link
Member

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:
Copy link
Contributor

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 _.

Copy link
Contributor Author

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:
Copy link
Contributor

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

Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

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:
Copy link
Contributor

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?

Copy link
Contributor Author

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:
Copy link
Contributor

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}) {}
Copy link
Contributor Author

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:
Copy link
Contributor

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"; |]
Copy link
Contributor

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.

Copy link
Contributor Author

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:
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@paulvanbrenk paulvanbrenk merged commit 57feab3 into master Nov 15, 2016
@paulvanbrenk paulvanbrenk deleted the unusedidentifier branch November 15, 2016 18:30
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
4 participants