fixUnusedIdentifier: If every VariableDeclaration is unused, remove the VariableStatement (#24231)

This commit is contained in:
Andy
2018-05-21 15:54:33 -07:00
committed by GitHub
parent d2f6f6a0a4
commit 802dc2bb9b
11 changed files with 129 additions and 63 deletions

View File

@@ -22620,14 +22620,6 @@ namespace ts {
function errorUnusedLocal(declaration: Declaration, name: string, addDiagnostic: AddUnusedDiagnostic) {
const node = getNameOfDeclaration(declaration) || declaration;
if (isIdentifierThatStartsWithUnderScore(node)) {
const declaration = getRootDeclaration(node.parent);
if ((declaration.kind === SyntaxKind.VariableDeclaration && isForInOrOfStatement(declaration.parent.parent)) ||
declaration.kind === SyntaxKind.TypeParameter) {
return;
}
}
const message = isTypeDeclaration(declaration) ? Diagnostics._0_is_declared_but_never_used : Diagnostics._0_is_declared_but_its_value_is_never_read;
addDiagnostic(UnusedKind.Local, createDiagnosticForNodeSpan(getSourceFileOfNode(declaration), declaration, node, message, name));
}
@@ -22712,6 +22704,7 @@ namespace ts {
// Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value.
const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>();
const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>();
const unusedVariables = createMap<[VariableDeclarationList, VariableDeclaration[]]>();
nodeWithLocals.locals.forEach(local => {
// If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`.
// If it's a type parameter merged with a parameter, check if the parameter-side is used.
@@ -22731,6 +22724,11 @@ namespace ts {
addToGroup(unusedDestructures, declaration.parent, declaration, getNodeId);
}
}
else if (isVariableDeclaration(declaration)) {
if (!isIdentifierThatStartsWithUnderScore(declaration.name) || !isForInOrOfStatement(declaration.parent.parent)) {
addToGroup(unusedVariables, declaration.parent, declaration, getNodeId);
}
}
else {
const parameter = local.valueDeclaration && tryGetRootParameterDeclaration(local.valueDeclaration);
if (parameter) {
@@ -22747,32 +22745,63 @@ namespace ts {
});
unusedImports.forEach(([importClause, unuseds]) => {
const importDecl = importClause.parent;
if (forEachImportedDeclaration(importClause, d => !contains(unuseds, d))) {
for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name), addDiagnostic);
}
else if (unuseds.length === 1) {
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name)));
const nDeclarations = (importClause.name ? 1 : 0) +
(importClause.namedBindings ?
(importClause.namedBindings.kind === SyntaxKind.NamespaceImport ? 1 : importClause.namedBindings.elements.length)
: 0);
if (nDeclarations === unuseds.length) {
addDiagnostic(UnusedKind.Local, unuseds.length === 1
? createDiagnosticForNode(importDecl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(first(unuseds).name))
: createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused));
}
else {
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(importDecl, Diagnostics.All_imports_in_import_declaration_are_unused));
for (const unused of unuseds) errorUnusedLocal(unused, idText(unused.name), addDiagnostic);
}
});
unusedDestructures.forEach(([bindingPattern, bindingElements]) => {
const kind = tryGetRootParameterDeclaration(bindingPattern.parent) ? UnusedKind.Parameter : UnusedKind.Local;
if (!bindingPattern.elements.every(e => contains(bindingElements, e))) {
if (bindingPattern.elements.length === bindingElements.length) {
if (bindingElements.length === 1 && bindingPattern.parent.kind === SyntaxKind.VariableDeclaration && bindingPattern.parent.parent.kind === SyntaxKind.VariableDeclarationList) {
addToGroup(unusedVariables, bindingPattern.parent.parent, bindingPattern.parent, getNodeId);
}
else {
addDiagnostic(kind, bindingElements.length === 1
? createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(cast(first(bindingElements).name, isIdentifier)))
: createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused));
}
}
else {
for (const e of bindingElements) {
addDiagnostic(kind, createDiagnosticForNode(e, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(cast(e.name, isIdentifier))));
}
}
else if (bindingElements.length === 1) {
addDiagnostic(kind, createDiagnosticForNode(bindingPattern, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(cast(first(bindingElements).name, isIdentifier))));
});
unusedVariables.forEach(([declarationList, declarations]) => {
if (declarationList.declarations.length === declarations.length) {
addDiagnostic(UnusedKind.Local, declarations.length === 1
? createDiagnosticForNode(first(declarations).name, Diagnostics._0_is_declared_but_its_value_is_never_read, bindingNameText(first(declarations).name))
: createDiagnosticForNode(declarationList.parent.kind === SyntaxKind.VariableStatement ? declarationList.parent : declarationList, Diagnostics.All_variables_are_unused));
}
else {
addDiagnostic(kind, createDiagnosticForNode(bindingPattern, Diagnostics.All_destructured_elements_are_unused));
for (const decl of declarations) {
addDiagnostic(UnusedKind.Local, createDiagnosticForNode(decl, Diagnostics._0_is_declared_but_its_value_is_never_read, idText(cast(decl.name, isIdentifier))));
}
}
});
}
function bindingNameText(name: BindingName): string {
switch (name.kind) {
case SyntaxKind.Identifier:
return idText(name);
case SyntaxKind.ArrayBindingPattern:
case SyntaxKind.ObjectBindingPattern:
return bindingNameText(cast(first(name.elements), isBindingElement).name);
default:
return Debug.assertNever(name);
}
}
type ImportedDeclaration = ImportClause | ImportSpecifier | NamespaceImport;
function isImportedDeclaration(node: Node): node is ImportedDeclaration {
return node.kind === SyntaxKind.ImportClause || node.kind === SyntaxKind.ImportSpecifier || node.kind === SyntaxKind.NamespaceImport;
@@ -22781,12 +22810,6 @@ namespace ts {
return decl.kind === SyntaxKind.ImportClause ? decl : decl.kind === SyntaxKind.NamespaceImport ? decl.parent : decl.parent.parent;
}
function forEachImportedDeclaration<T>(importClause: ImportClause, cb: (im: ImportedDeclaration) => T | undefined): T | undefined {
const { name: defaultName, namedBindings } = importClause;
return (defaultName && cb(importClause)) ||
namedBindings && (namedBindings.kind === SyntaxKind.NamespaceImport ? cb(namedBindings) : forEach(namedBindings.elements, cb));
}
function checkBlock(node: Block) {
// Grammar checking for SyntaxKind.Block
if (node.kind === SyntaxKind.Block) {

View File

@@ -3556,6 +3556,11 @@
"code": 6198,
"reportsUnnecessary": true
},
"All variables are unused.": {
"category": "Error",
"code": 6199,
"reportsUnnecessary": true
},
"Projects to reference": {
"category": "Message",
@@ -3994,6 +3999,10 @@
"category": "Message",
"code": 90009
},
"Remove variable statement": {
"category": "Message",
"code": 90010
},
"Import '{0}' from module \"{1}\"": {
"category": "Message",
"code": 90013