📄 collectUnusedVariables¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 31 |
| 🧱 Classes | 1 |
| 📦 Imports | 13 |
| 📊 Variables & Constants | 2 |
| 📐 Interfaces | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/util/collectUnusedVariables.ts
📦 Imports¶
| Name | Source |
|---|---|
ScopeManager |
@typescript-eslint/scope-manager |
ScopeVariable |
@typescript-eslint/scope-manager |
TSESTree |
@typescript-eslint/utils |
ImplicitLibVariable |
@typescript-eslint/scope-manager |
ScopeType |
@typescript-eslint/scope-manager |
Visitor |
@typescript-eslint/scope-manager |
AST_NODE_TYPES |
@typescript-eslint/utils |
ASTUtils |
@typescript-eslint/utils |
ESLintUtils |
@typescript-eslint/utils |
TSESLint |
@typescript-eslint/utils |
isTypeImport |
./isTypeImport |
referenceContainsTypePredicate |
./referenceContainsTypePredicate |
referenceContainsTypeQuery |
./referenceContainsTypeQuery |
Variables & Constants¶
| Name | Type | Kind | Value | Exported |
|---|---|---|---|---|
MERGEABLE_TYPES |
Set<any> |
const | new Set([ AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.FunctionDeclaration... |
✗ |
LOGICAL_ASSIGNMENT_OPERATORS |
Set<string> |
const | new Set(['??=', '&&=', '\|\|=']) |
✗ |
Functions¶
collectVariables(context: Readonly<TSESLint.RuleContext<MessageId…): VariableAnalysis¶
Collects the set of unused variables for a given context.
Due to complexity, this does not take into consideration: - variables within declaration files - variables within ambient module declarations
Raw JSDoc
/**
* Collects the set of unused variables for a given context.
*
* Due to complexity, this does not take into consideration:
* - variables within declaration files
* - variables within ambient module declarations
*/
Calls:
UnusedVarsVisitor.collectUnusedVariablesESLintUtils.nullThrows
Code
export function collectVariables<
MessageIds extends string,
Options extends readonly unknown[],
>(
context: Readonly<TSESLint.RuleContext<MessageIds, Options>>,
): VariableAnalysis {
return UnusedVarsVisitor.collectUnusedVariables(
context.sourceCode.ast,
ESLintUtils.nullThrows(
context.sourceCode.scopeManager,
'Missing required scope manager',
),
);
}
UnusedVarsVisitor.collectUnusedVariables(program: TSESTree.Program, scopeManager: ScopeManager): VariableAnalysis¶
Parameters:
programTSESTree.ProgramscopeManagerScopeManager
Returns: VariableAnalysis
Calls:
this.RESULTS_CACHE.getvisitor.visitvisitor.collectUnusedVariablesvisitor.getScopethis.RESULTS_CACHE.set
Code
public static collectUnusedVariables(
program: TSESTree.Program,
scopeManager: ScopeManager,
): VariableAnalysis {
const cached = this.RESULTS_CACHE.get(program);
if (cached) {
return cached;
}
const visitor = new this(scopeManager);
visitor.visit(program);
const unusedVars = visitor.collectUnusedVariables(
visitor.getScope(program),
);
this.RESULTS_CACHE.set(program, unusedVars);
return unusedVars;
}
UnusedVarsVisitor.Identifier(node: TSESTree.Identifier): void¶
Parameters:
nodeTSESTree.Identifier
Returns: void
Calls:
this.getScopescope.block.params.includesthis.markVariableAsUsed
Internal Comments:
Code
protected Identifier(node: TSESTree.Identifier): void {
const scope = this.getScope(node);
if (
scope.type === TSESLint.Scope.ScopeType.function &&
node.name === 'this' &&
// this parameters should always be considered used as they're pseudo-parameters
'params' in scope.block &&
scope.block.params.includes(node)
) {
this.markVariableAsUsed(node);
}
}
UnusedVarsVisitor.TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void¶
Parameters:
nodeTSESTree.TSEnumDeclaration
Returns: void
Calls:
this.getScopethis.markVariableAsUsed
Internal Comments:
// enum members create variables because they can be referenced within the enum, (x2)
// but they obviously aren't unused variables for the purposes of this rule. (x2)
Code
protected TSEnumDeclaration(node: TSESTree.TSEnumDeclaration): void {
// enum members create variables because they can be referenced within the enum,
// but they obviously aren't unused variables for the purposes of this rule.
const scope = this.getScope(node);
for (const variable of scope.variables) {
this.markVariableAsUsed(variable);
}
}
UnusedVarsVisitor.TSMappedType(node: TSESTree.TSMappedType): void¶
Parameters:
nodeTSESTree.TSMappedType
Returns: void
Calls:
this.markVariableAsUsed
Internal Comments:
// mapped types create a variable for their type name, but it's not necessary to reference it, (x4)
// so we shouldn't consider it as unused for the purpose of this rule. (x4)
Code
UnusedVarsVisitor.TSModuleDeclaration(node: TSESTree.TSModuleDeclaration): void¶
Parameters:
nodeTSESTree.TSModuleDeclaration
Returns: void
Calls:
this.markVariableAsUsed
Internal Comments:
Code
UnusedVarsVisitor.TSParameterProperty(node: TSESTree.TSParameterProperty): void¶
Parameters:
nodeTSESTree.TSParameterProperty
Returns: void
Calls:
this.markVariableAsUsed
Code
protected TSParameterProperty(node: TSESTree.TSParameterProperty): void {
let identifier: TSESTree.Identifier;
switch (node.parameter.type) {
case AST_NODE_TYPES.AssignmentPattern:
identifier = node.parameter.left;
break;
case AST_NODE_TYPES.Identifier:
identifier = node.parameter;
break;
}
this.markVariableAsUsed(identifier);
}
UnusedVarsVisitor.getScope(currentNode: TSESTree.Node): TSESLint.Scope.Scope¶
Parameters:
currentNodeTSESTree.Node
Returns: TSESLint.Scope.Scope
Calls:
this.#scopeManager.acquire
Internal Comments:
// On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. (x2)
Code
private getScope(currentNode: TSESTree.Node): TSESLint.Scope.Scope {
// On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
const inner = currentNode.type !== AST_NODE_TYPES.Program;
let node: TSESTree.Node | undefined = currentNode;
while (node) {
const scope = this.#scopeManager.acquire(node, inner);
if (scope) {
if (scope.type === ScopeType.functionExpressionName) {
return scope.childScopes[0];
}
return scope;
}
node = node.parent;
}
return this.#scopeManager.scopes[0];
}
UnusedVarsVisitor.markVariableAsUsed(variableOrIdentifier: ScopeVariable | TSESTree.Identifier): void¶
Parameters:
variableOrIdentifierScopeVariable | TSESTree.Identifier
Returns: void
Code
UnusedVarsVisitor.visitClass(node: TSESTree.ClassDeclaration | TSESTree.Cl…): void¶
Parameters:
nodeTSESTree.ClassDeclaration | TSESTree.ClassExpression
Returns: void
Calls:
this.getScopethis.markVariableAsUsed
Internal Comments:
Code
private visitClass(
node: TSESTree.ClassDeclaration | TSESTree.ClassExpression,
): void {
// skip a variable of class itself name in the class scope
const scope = this.getScope(node) as TSESLint.Scope.Scopes.ClassScope;
for (const variable of scope.variables) {
if (variable.identifiers[0] === scope.block.id) {
this.markVariableAsUsed(variable);
return;
}
}
}
UnusedVarsVisitor.visitForInForOf(node: TSESTree.ForInStatement | TSESTree.ForO…): void¶
Parameters:
nodeTSESTree.ForInStatement | TSESTree.ForOfStatement
Returns: void
Calls:
this.#scopeManager.getDeclaredVariables(node.left).atthis.markVariableAsUsed
Internal Comments:
/**
* (Brad Zacher): I hate that this has to exist.
* But it is required for compat with the base ESLint rule.
*
* In 2015, ESLint decided to add an exception for these two specific cases
* ```
* for (var key in object) return;
*
* var key;
* for (key in object) return;
* ```
*
* I disagree with it, but what are you going to do...
*
* https://github.com/eslint/eslint/issues/2342
*/ (x2)
Code
private visitForInForOf(
node: TSESTree.ForInStatement | TSESTree.ForOfStatement,
): void {
/**
* (Brad Zacher): I hate that this has to exist.
* But it is required for compat with the base ESLint rule.
*
* In 2015, ESLint decided to add an exception for these two specific cases
* ```
* for (var key in object) return;
*
* var key;
* for (key in object) return;
* ```
*
* I disagree with it, but what are you going to do...
*
* https://github.com/eslint/eslint/issues/2342
*/
let idOrVariable;
if (node.left.type === AST_NODE_TYPES.VariableDeclaration) {
const variable = this.#scopeManager.getDeclaredVariables(node.left).at(0);
if (!variable) {
return;
}
idOrVariable = variable;
}
if (node.left.type === AST_NODE_TYPES.Identifier) {
idOrVariable = node.left;
}
if (idOrVariable == null) {
return;
}
let body = node.body;
if (node.body.type === AST_NODE_TYPES.BlockStatement) {
if (node.body.body.length !== 1) {
return;
}
body = node.body.body[0];
}
if (body.type !== AST_NODE_TYPES.ReturnStatement) {
return;
}
this.markVariableAsUsed(idOrVariable);
}
UnusedVarsVisitor.visitFunction(node: TSESTree.FunctionDeclaration | TSESTree…): void¶
Parameters:
nodeTSESTree.FunctionDeclaration | TSESTree.FunctionExpression
Returns: void
Calls:
this.getScopescope.set.getthis.markVariableAsUsed
Internal Comments:
Code
UnusedVarsVisitor.visitFunctionTypeSignature(node: | TSESTree.TSCallSignatureDeclaration |…): void¶
Parameters:
node| TSESTree.TSCallSignatureDeclaration | TSESTree.TSConstructorType | TSESTree.TSConstructSignatureDeclaration | TSESTree.TSDeclareFunction | TSESTree.TSEmptyBodyFunctionExpression | TSESTree.TSFunctionType | TSESTree.TSMethodSignature
Returns: void
Calls:
this.visitPatternthis.markVariableAsUsed
Internal Comments:
// function type signature params create variables because they can be referenced within the signature,
// but they obviously aren't unused variables for the purposes of this rule.
Code
private visitFunctionTypeSignature(
node:
| TSESTree.TSCallSignatureDeclaration
| TSESTree.TSConstructorType
| TSESTree.TSConstructSignatureDeclaration
| TSESTree.TSDeclareFunction
| TSESTree.TSEmptyBodyFunctionExpression
| TSESTree.TSFunctionType
| TSESTree.TSMethodSignature,
): void {
// function type signature params create variables because they can be referenced within the signature,
// but they obviously aren't unused variables for the purposes of this rule.
for (const param of node.params) {
this.visitPattern(param, name => {
this.markVariableAsUsed(name);
});
}
}
UnusedVarsVisitor.visitSetter(node: TSESTree.MethodDefinition | TSESTree.Pr…): void¶
Parameters:
nodeTSESTree.MethodDefinition | TSESTree.Property
Returns: void
Calls:
this.visitPatternthis.markVariableAsUsed
Internal Comments:
Code
private visitSetter(
node: TSESTree.MethodDefinition | TSESTree.Property,
): void {
if (node.kind === 'set') {
// ignore setter parameters because they're syntactically required to exist
for (const param of (node.value as TSESTree.FunctionLike).params) {
this.visitPattern(param, id => {
this.markVariableAsUsed(id);
});
}
}
}
isInside(inner: TSESTree.Node, outer: TSESTree.Node): boolean¶
Checks the position of given nodes.
Parameters:
innerany: A node which is expected as inside.outerany: A node which is expected as outside.
Returns: undefined
true if the inner node exists in the outer node.
Raw JSDoc
Code
isSelfReference(ref: TSESLint.Scope.Reference, nodes: Set<TSESTree.Node>): boolean¶
Determine if an identifier is referencing an enclosing name. This only applies to declarations that create their own scope (modules, functions, classes)
Parameters:
refany: The reference to check.nodesany: The candidate function nodes.
Returns: undefined
True if it's a self-reference, false if not.
Raw JSDoc
/**
* Determine if an identifier is referencing an enclosing name.
* This only applies to declarations that create their own scope (modules, functions, classes)
* @param ref The reference to check.
* @param nodes The candidate function nodes.
* @returns True if it's a self-reference, false if not.
*/
Calls:
nodes.has
Code
isMergeableExported(variable: ScopeVariable): boolean¶
Determine if the variable is directly exported
Parameters:
variableany: the variable to check
Raw JSDoc
Calls:
MERGEABLE_TYPES.has
Internal Comments:
// If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one
// parameters can never be exported.
// their `node` prop points to the function decl, which can be exported
// so we need to special case them
Code
function isMergeableExported(variable: ScopeVariable): boolean {
// If all of the merged things are of the same type, TS will error if not all of them are exported - so we only need to find one
for (const def of variable.defs) {
// parameters can never be exported.
// their `node` prop points to the function decl, which can be exported
// so we need to special case them
if (def.type === TSESLint.Scope.DefinitionType.Parameter) {
continue;
}
if (
(MERGEABLE_TYPES.has(def.node.type) &&
def.node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration) ||
def.node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration
) {
return true;
}
}
return false;
}
isExported(variable: ScopeVariable): boolean¶
Determines if a given variable is being exported from a module.
Parameters:
variableany: eslint-scope variable object.
Returns: undefined
True if the variable is exported, false if not.
Raw JSDoc
Calls:
variable.defs.somenode.parent.type.startsWith
Code
function isExported(variable: ScopeVariable): boolean {
return variable.defs.some(definition => {
let node = definition.node;
if (node.type === AST_NODE_TYPES.VariableDeclarator) {
node = node.parent;
} else if (definition.type === TSESLint.Scope.DefinitionType.Parameter) {
return false;
}
return node.parent.type.startsWith('Export');
});
}
isUsedVariable(variable: ScopeVariable): boolean¶
Determines if the variable is used.
Parameters:
variableany: The variable to check.
Returns: undefined
True if the variable is used
Raw JSDoc
Calls:
variable.defs.forEachfunctionDefinitions.addnodes.addisInsideisUnusedExpressionASTUtils.isFunctionASTUtils.isLoopisInLoopparent.type.endsWithgetUpperFunctionisStorableFunctionref.isReadLOGICAL_ASSIGNMENT_OPERATORS.hasisInsideOfStorableFunctiongetFunctionDefinitionsgetTypeDeclarationsgetModuleDeclarationsgetEnumDeclarationsvariable.defs.everyvariable.references.someisReadForItselfgetRhsNodereferenceContainsTypeQuery (from ./referenceContainsTypeQuery)referenceContainsTypePredicate (from ./referenceContainsTypePredicate)isSelfReferenceisInsideOneOf
Internal Comments:
/**
* Gets a list of function definitions for a specified variable.
* @param variable eslint-scope variable object.
* @returns Function nodes.
*/
// FunctionDeclarations
// FunctionExpressions
/**
* Checks if the ref is contained within one of the given nodes
*/
/**
* Checks whether a given node is unused expression or not.
* @param node The node itself
* @returns The node is an unused expression.
*/
/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* In the following cases, this returns null.
*
* - The reference is not the LHS of an assignment expression.
* - The reference is inside of a loop.
* - The reference is inside of a function scope which is different from
* the declaration.
* @param ref A reference to check.
* @param prevRhsNode The previous RHS node.
* @returns The RHS node or null.
*/
/**
* Checks whether the given node is in a loop or not.
* @param node The node to check.
* @returns `true` if the node is in a loop.
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
/**
* Checks whether a given reference is a read to update itself or not.
* @param ref A reference to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns The reference is a read to update itself.
*/
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
// self update. e.g. `a += 1`, `a++`
Code
function isUsedVariable(variable: ScopeVariable): boolean {
/**
* Gets a list of function definitions for a specified variable.
* @param variable eslint-scope variable object.
* @returns Function nodes.
*/
function getFunctionDefinitions(variable: ScopeVariable): Set<TSESTree.Node> {
const functionDefinitions = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
// FunctionDeclarations
if (def.type === TSESLint.Scope.DefinitionType.FunctionName) {
functionDefinitions.add(def.node);
}
// FunctionExpressions
if (
def.type === TSESLint.Scope.DefinitionType.Variable &&
(def.node.init?.type === AST_NODE_TYPES.FunctionExpression ||
def.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression)
) {
functionDefinitions.add(def.node.init);
}
});
return functionDefinitions;
}
function getTypeDeclarations(variable: ScopeVariable): Set<TSESTree.Node> {
const nodes = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
if (
def.node.type === AST_NODE_TYPES.TSInterfaceDeclaration ||
def.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration
) {
nodes.add(def.node);
}
});
return nodes;
}
function getModuleDeclarations(variable: ScopeVariable): Set<TSESTree.Node> {
const nodes = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
if (def.node.type === AST_NODE_TYPES.TSModuleDeclaration) {
nodes.add(def.node);
}
});
return nodes;
}
function getEnumDeclarations(variable: ScopeVariable): Set<TSESTree.Node> {
const nodes = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
if (def.node.type === AST_NODE_TYPES.TSEnumDeclaration) {
nodes.add(def.node);
}
});
return nodes;
}
/**
* Checks if the ref is contained within one of the given nodes
*/
function isInsideOneOf(
ref: TSESLint.Scope.Reference,
nodes: Set<TSESTree.Node>,
): boolean {
for (const node of nodes) {
if (isInside(ref.identifier, node)) {
return true;
}
}
return false;
}
/**
* Checks whether a given node is unused expression or not.
* @param node The node itself
* @returns The node is an unused expression.
*/
function isUnusedExpression(node: TSESTree.Expression): boolean {
const parent = node.parent;
if (parent.type === AST_NODE_TYPES.ExpressionStatement) {
return true;
}
if (parent.type === AST_NODE_TYPES.SequenceExpression) {
const isLastExpression =
parent.expressions[parent.expressions.length - 1] === node;
if (!isLastExpression) {
return true;
}
return isUnusedExpression(parent);
}
return false;
}
/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* In the following cases, this returns null.
*
* - The reference is not the LHS of an assignment expression.
* - The reference is inside of a loop.
* - The reference is inside of a function scope which is different from
* the declaration.
* @param ref A reference to check.
* @param prevRhsNode The previous RHS node.
* @returns The RHS node or null.
*/
function getRhsNode(
ref: TSESLint.Scope.Reference,
prevRhsNode: TSESTree.Node | null,
): TSESTree.Node | null {
/**
* Checks whether the given node is in a loop or not.
* @param node The node to check.
* @returns `true` if the node is in a loop.
*/
function isInLoop(node: TSESTree.Node): boolean {
let currentNode: TSESTree.Node | undefined = node;
while (currentNode) {
if (ASTUtils.isFunction(currentNode)) {
break;
}
if (ASTUtils.isLoop(currentNode)) {
return true;
}
currentNode = currentNode.parent;
}
return false;
}
const id = ref.identifier;
const parent = id.parent;
const refScope = ref.from.variableScope;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const varScope = ref.resolved!.scope.variableScope;
const canBeUsedLater = refScope !== varScope || isInLoop(id);
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
if (prevRhsNode && isInside(id, prevRhsNode)) {
return prevRhsNode;
}
if (
parent.type === AST_NODE_TYPES.AssignmentExpression &&
isUnusedExpression(parent) &&
id === parent.left &&
!canBeUsedLater
) {
return parent.right;
}
return null;
}
/**
* Checks whether a given reference is a read to update itself or not.
* @param ref A reference to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns The reference is a read to update itself.
*/
function isReadForItself(
ref: TSESLint.Scope.Reference,
rhsNode: TSESTree.Node | null,
): boolean {
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
function isInsideOfStorableFunction(
id: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
function getUpperFunction(node: TSESTree.Node): TSESTree.Node | null {
let currentNode: TSESTree.Node | undefined = node;
while (currentNode) {
if (ASTUtils.isFunction(currentNode)) {
return currentNode;
}
currentNode = currentNode.parent;
}
return null;
}
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
function isStorableFunction(
funcNode: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
let node = funcNode;
let parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case AST_NODE_TYPES.SequenceExpression:
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case AST_NODE_TYPES.CallExpression:
case AST_NODE_TYPES.NewExpression:
return parent.callee !== node;
case AST_NODE_TYPES.AssignmentExpression:
case AST_NODE_TYPES.TaggedTemplateExpression:
case AST_NODE_TYPES.YieldExpression:
return true;
default:
if (
parent.type.endsWith('Statement') ||
parent.type.endsWith('Declaration')
) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
const funcNode = getUpperFunction(id);
return (
!!funcNode &&
isInside(funcNode, rhsNode) &&
isStorableFunction(funcNode, rhsNode)
);
}
const id = ref.identifier;
const parent = id.parent;
return (
ref.isRead() && // in RHS of an assignment for itself. e.g. `a = a + 1`
// self update. e.g. `a += 1`, `a++`
((parent.type === AST_NODE_TYPES.AssignmentExpression &&
!LOGICAL_ASSIGNMENT_OPERATORS.has(parent.operator) &&
isUnusedExpression(parent) &&
parent.left === id) ||
(parent.type === AST_NODE_TYPES.UpdateExpression &&
isUnusedExpression(parent)) ||
(!!rhsNode &&
isInside(id, rhsNode) &&
!isInsideOfStorableFunction(id, rhsNode)))
);
}
const functionNodes = getFunctionDefinitions(variable);
const isFunctionDefinition = functionNodes.size > 0;
const typeDeclNodes = getTypeDeclarations(variable);
const isTypeDecl = typeDeclNodes.size > 0;
const moduleDeclNodes = getModuleDeclarations(variable);
const isModuleDecl = moduleDeclNodes.size > 0;
const enumDeclNodes = getEnumDeclarations(variable);
const isEnumDecl = enumDeclNodes.size > 0;
const isImportedAsType = variable.defs.every(isTypeImport);
let rhsNode: TSESTree.Node | null = null;
return variable.references.some(ref => {
const forItself = isReadForItself(ref, rhsNode);
rhsNode = getRhsNode(ref, rhsNode);
return (
ref.isRead() &&
!forItself &&
!(
!isImportedAsType &&
(referenceContainsTypeQuery(ref.identifier) ||
referenceContainsTypePredicate(ref.identifier))
) &&
!(isFunctionDefinition && isSelfReference(ref, functionNodes)) &&
!(isTypeDecl && isInsideOneOf(ref, typeDeclNodes)) &&
!(isModuleDecl && isSelfReference(ref, moduleDeclNodes)) &&
!(isEnumDecl && isSelfReference(ref, enumDeclNodes))
);
});
}
Internal helpers¶
Declared inside another function in this file.
getFunctionDefinitions(variable: ScopeVariable): Set<TSESTree.Node>¶
Gets a list of function definitions for a specified variable.
Parameters:
variableany: eslint-scope variable object.
Returns: undefined
Function nodes.
Raw JSDoc
Calls:
variable.defs.forEachfunctionDefinitions.add
Internal Comments:
Code
function getFunctionDefinitions(variable: ScopeVariable): Set<TSESTree.Node> {
const functionDefinitions = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
// FunctionDeclarations
if (def.type === TSESLint.Scope.DefinitionType.FunctionName) {
functionDefinitions.add(def.node);
}
// FunctionExpressions
if (
def.type === TSESLint.Scope.DefinitionType.Variable &&
(def.node.init?.type === AST_NODE_TYPES.FunctionExpression ||
def.node.init?.type === AST_NODE_TYPES.ArrowFunctionExpression)
) {
functionDefinitions.add(def.node.init);
}
});
return functionDefinitions;
}
getTypeDeclarations(variable: ScopeVariable): Set<TSESTree.Node>¶
Parameters:
variableScopeVariable
Returns: Set<TSESTree.Node>
Calls:
variable.defs.forEachnodes.add
Code
function getTypeDeclarations(variable: ScopeVariable): Set<TSESTree.Node> {
const nodes = new Set<TSESTree.Node>();
variable.defs.forEach(def => {
if (
def.node.type === AST_NODE_TYPES.TSInterfaceDeclaration ||
def.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration
) {
nodes.add(def.node);
}
});
return nodes;
}
getModuleDeclarations(variable: ScopeVariable): Set<TSESTree.Node>¶
Parameters:
variableScopeVariable
Returns: Set<TSESTree.Node>
Calls:
variable.defs.forEachnodes.add
Code
getEnumDeclarations(variable: ScopeVariable): Set<TSESTree.Node>¶
Parameters:
variableScopeVariable
Returns: Set<TSESTree.Node>
Calls:
variable.defs.forEachnodes.add
Code
isInsideOneOf(ref: TSESLint.Scope.Reference, nodes: Set<TSESTree.Node>): boolean¶
Checks if the ref is contained within one of the given nodes
Calls:
isInside
Code
isUnusedExpression(node: TSESTree.Expression): boolean¶
Checks whether a given node is unused expression or not.
Parameters:
nodeany: The node itself
Returns: undefined
The node is an unused expression.
Raw JSDoc
Calls:
isUnusedExpression
Code
function isUnusedExpression(node: TSESTree.Expression): boolean {
const parent = node.parent;
if (parent.type === AST_NODE_TYPES.ExpressionStatement) {
return true;
}
if (parent.type === AST_NODE_TYPES.SequenceExpression) {
const isLastExpression =
parent.expressions[parent.expressions.length - 1] === node;
if (!isLastExpression) {
return true;
}
return isUnusedExpression(parent);
}
return false;
}
getRhsNode(ref: TSESLint.Scope.Reference, prevRhsNode: TSESTree.Node | null): TSESTree.Node | null¶
If a given reference is left-hand side of an assignment, this gets the right-hand side node of the assignment.
In the following cases, this returns null.
- The reference is not the LHS of an assignment expression.
- The reference is inside of a loop.
- The reference is inside of a function scope which is different from the declaration.
Parameters:
refany: A reference to check.prevRhsNodeany: The previous RHS node.
Returns: undefined
The RHS node or null.
Raw JSDoc
/**
* If a given reference is left-hand side of an assignment, this gets
* the right-hand side node of the assignment.
*
* In the following cases, this returns null.
*
* - The reference is not the LHS of an assignment expression.
* - The reference is inside of a loop.
* - The reference is inside of a function scope which is different from
* the declaration.
* @param ref A reference to check.
* @param prevRhsNode The previous RHS node.
* @returns The RHS node or null.
*/
Calls:
ASTUtils.isFunctionASTUtils.isLoopisInLoopisInsideisUnusedExpression
Internal Comments:
/**
* Checks whether the given node is in a loop or not.
* @param node The node to check.
* @returns `true` if the node is in a loop.
*/
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion (x2)
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
Code
function getRhsNode(
ref: TSESLint.Scope.Reference,
prevRhsNode: TSESTree.Node | null,
): TSESTree.Node | null {
/**
* Checks whether the given node is in a loop or not.
* @param node The node to check.
* @returns `true` if the node is in a loop.
*/
function isInLoop(node: TSESTree.Node): boolean {
let currentNode: TSESTree.Node | undefined = node;
while (currentNode) {
if (ASTUtils.isFunction(currentNode)) {
break;
}
if (ASTUtils.isLoop(currentNode)) {
return true;
}
currentNode = currentNode.parent;
}
return false;
}
const id = ref.identifier;
const parent = id.parent;
const refScope = ref.from.variableScope;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const varScope = ref.resolved!.scope.variableScope;
const canBeUsedLater = refScope !== varScope || isInLoop(id);
/*
* Inherits the previous node if this reference is in the node.
* This is for `a = a + a`-like code.
*/
if (prevRhsNode && isInside(id, prevRhsNode)) {
return prevRhsNode;
}
if (
parent.type === AST_NODE_TYPES.AssignmentExpression &&
isUnusedExpression(parent) &&
id === parent.left &&
!canBeUsedLater
) {
return parent.right;
}
return null;
}
isInLoop(node: TSESTree.Node): boolean¶
Checks whether the given node is in a loop or not.
Parameters:
nodeany: The node to check.
Returns: undefined
true if the node is in a loop.
Raw JSDoc
Calls:
ASTUtils.isFunctionASTUtils.isLoop
Code
isReadForItself(ref: TSESLint.Scope.Reference, rhsNode: TSESTree.Node | null): boolean¶
Checks whether a given reference is a read to update itself or not.
Parameters:
refany: A reference to check.rhsNodeany: The RHS node of the previous assignment.
Returns: undefined
The reference is a read to update itself.
Raw JSDoc
Calls:
ASTUtils.isFunctionisInsideparent.type.endsWithgetUpperFunctionisStorableFunctionref.isReadLOGICAL_ASSIGNMENT_OPERATORS.hasisUnusedExpressionisInsideOfStorableFunction
Internal Comments:
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
// self update. e.g. `a += 1`, `a++`
Code
function isReadForItself(
ref: TSESLint.Scope.Reference,
rhsNode: TSESTree.Node | null,
): boolean {
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
function isInsideOfStorableFunction(
id: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
function getUpperFunction(node: TSESTree.Node): TSESTree.Node | null {
let currentNode: TSESTree.Node | undefined = node;
while (currentNode) {
if (ASTUtils.isFunction(currentNode)) {
return currentNode;
}
currentNode = currentNode.parent;
}
return null;
}
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
function isStorableFunction(
funcNode: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
let node = funcNode;
let parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case AST_NODE_TYPES.SequenceExpression:
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case AST_NODE_TYPES.CallExpression:
case AST_NODE_TYPES.NewExpression:
return parent.callee !== node;
case AST_NODE_TYPES.AssignmentExpression:
case AST_NODE_TYPES.TaggedTemplateExpression:
case AST_NODE_TYPES.YieldExpression:
return true;
default:
if (
parent.type.endsWith('Statement') ||
parent.type.endsWith('Declaration')
) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
const funcNode = getUpperFunction(id);
return (
!!funcNode &&
isInside(funcNode, rhsNode) &&
isStorableFunction(funcNode, rhsNode)
);
}
const id = ref.identifier;
const parent = id.parent;
return (
ref.isRead() && // in RHS of an assignment for itself. e.g. `a = a + 1`
// self update. e.g. `a += 1`, `a++`
((parent.type === AST_NODE_TYPES.AssignmentExpression &&
!LOGICAL_ASSIGNMENT_OPERATORS.has(parent.operator) &&
isUnusedExpression(parent) &&
parent.left === id) ||
(parent.type === AST_NODE_TYPES.UpdateExpression &&
isUnusedExpression(parent)) ||
(!!rhsNode &&
isInside(id, rhsNode) &&
!isInsideOfStorableFunction(id, rhsNode)))
);
}
isInsideOfStorableFunction(id: TSESTree.Node, rhsNode: TSESTree.Node): boolean¶
Checks whether a given Identifier node exists inside of a function node which can be used later.
"can be used later" means: - the function is assigned to a variable. - the function is bound to a property and the object can be used later. - the function is bound as an argument of a function call.
If a reference exists in a function which can be used later, the reference is read when the function is called.
Parameters:
idany: An Identifier node to check.rhsNodeany: The RHS node of the previous assignment.
Returns: undefined
true if the id node exists inside of a function node which can be used later.
Raw JSDoc
/**
* Checks whether a given Identifier node exists inside of a function node which can be used later.
*
* "can be used later" means:
* - the function is assigned to a variable.
* - the function is bound to a property and the object can be used later.
* - the function is bound as an argument of a function call.
*
* If a reference exists in a function which can be used later, the reference is read when the function is called.
* @param id An Identifier node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if the `id` node exists inside of a function node which can be used later.
*/
Calls:
ASTUtils.isFunctionisInsideparent.type.endsWithgetUpperFunctionisStorableFunction
Internal Comments:
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
Code
function isInsideOfStorableFunction(
id: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
/**
* Finds a function node from ancestors of a node.
* @param node A start node to find.
* @returns A found function node.
*/
function getUpperFunction(node: TSESTree.Node): TSESTree.Node | null {
let currentNode: TSESTree.Node | undefined = node;
while (currentNode) {
if (ASTUtils.isFunction(currentNode)) {
return currentNode;
}
currentNode = currentNode.parent;
}
return null;
}
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
function isStorableFunction(
funcNode: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
let node = funcNode;
let parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case AST_NODE_TYPES.SequenceExpression:
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case AST_NODE_TYPES.CallExpression:
case AST_NODE_TYPES.NewExpression:
return parent.callee !== node;
case AST_NODE_TYPES.AssignmentExpression:
case AST_NODE_TYPES.TaggedTemplateExpression:
case AST_NODE_TYPES.YieldExpression:
return true;
default:
if (
parent.type.endsWith('Statement') ||
parent.type.endsWith('Declaration')
) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
const funcNode = getUpperFunction(id);
return (
!!funcNode &&
isInside(funcNode, rhsNode) &&
isStorableFunction(funcNode, rhsNode)
);
}
getUpperFunction(node: TSESTree.Node): TSESTree.Node | null¶
Finds a function node from ancestors of a node.
Parameters:
nodeany: A start node to find.
Returns: undefined
A found function node.
Raw JSDoc
Calls:
ASTUtils.isFunction
Code
isStorableFunction(funcNode: TSESTree.Node, rhsNode: TSESTree.Node): boolean¶
Checks whether a given function node is stored to somewhere or not. If the function node is stored, the function can be used later.
Parameters:
funcNodeany: A function node to check.rhsNodeany: The RHS node of the previous assignment.
Returns: undefined
true if under the following conditions:
- the funcNode is assigned to a variable.
- the funcNode is bound as an argument of a function call.
- the function is bound to a property and the object satisfies above conditions.
Raw JSDoc
/**
* Checks whether a given function node is stored to somewhere or not.
* If the function node is stored, the function can be used later.
* @param funcNode A function node to check.
* @param rhsNode The RHS node of the previous assignment.
* @returns `true` if under the following conditions:
* - the funcNode is assigned to a variable.
* - the funcNode is bound as an argument of a function call.
* - the function is bound to a property and the object satisfies above conditions.
*/
Calls:
isInsideparent.type.endsWith
Internal Comments:
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
Code
function isStorableFunction(
funcNode: TSESTree.Node,
rhsNode: TSESTree.Node,
): boolean {
let node = funcNode;
let parent = funcNode.parent;
while (parent && isInside(parent, rhsNode)) {
switch (parent.type) {
case AST_NODE_TYPES.SequenceExpression:
if (parent.expressions[parent.expressions.length - 1] !== node) {
return false;
}
break;
case AST_NODE_TYPES.CallExpression:
case AST_NODE_TYPES.NewExpression:
return parent.callee !== node;
case AST_NODE_TYPES.AssignmentExpression:
case AST_NODE_TYPES.TaggedTemplateExpression:
case AST_NODE_TYPES.YieldExpression:
return true;
default:
if (
parent.type.endsWith('Statement') ||
parent.type.endsWith('Declaration')
) {
/*
* If it encountered statements, this is a complex pattern.
* Since analyzing complex patterns is hard, this returns `true` to avoid false positive.
*/
return true;
}
}
node = parent;
parent = parent.parent;
}
return false;
}
Classes¶
UnusedVarsVisitor¶
This class leverages an AST visitor to mark variables as used via the
eslintUsed property.
Extends: Visitor
Methods (16) — full entries under Functions
| Method | Signature |
|---|---|
collectUnusedVariables |
(program: TSESTree.Program, scopeManager: ScopeManager): VariableAnalysis |
Identifier |
(node: TSESTree.Identifier): void |
TSEnumDeclaration |
(node: TSESTree.TSEnumDeclaration): void |
TSMappedType |
(node: TSESTree.TSMappedType): void |
TSModuleDeclaration |
(node: TSESTree.TSModuleDeclaration): void |
TSParameterProperty |
(node: TSESTree.TSParameterProperty): void |
collectUnusedVariables |
(scope: TSESLint.Scope.Scope, variables: MutableVariableAnalysis): VariableAnalysis |
getScope |
(currentNode: TSESTree.Node): TSESLint.Scope.Scope |
markVariableAsUsed |
(variableOrIdentifier: ScopeVariable \| TSESTree.Identifier): void |
markVariableAsUsed |
(name: string, parent: TSESTree.Node): void |
markVariableAsUsed |
(variableOrIdentifierOrName: string \| ScopeVariable \| TSESTree.Identifier, parent: TSESTree.Nod... |
visitClass |
(node: TSESTree.ClassDeclaration \| TSESTree.ClassExpression): void |
visitForInForOf |
(node: TSESTree.ForInStatement \| TSESTree.ForOfStatement): void |
visitFunction |
(node: TSESTree.FunctionDeclaration \| TSESTree.FunctionExpression): void |
visitFunctionTypeSignature |
(node: \| TSESTree.TSCallSignatureDeclaration \| TSESTree.TSConstructorType \| TSESTree.TSConstru... |
visitSetter |
(node: TSESTree.MethodDefinition \| TSESTree.Property): void |
Interfaces¶
VariableAnalysis¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
unusedVariables |
ReadonlySet<ScopeVariable> |
✗ | not shown |
usedVariables |
ReadonlySet<ScopeVariable> |
✗ | not shown |
MutableVariableAnalysis¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
unusedVariables |
Set<ScopeVariable> |
✗ | not shown |
usedVariables |
Set<ScopeVariable> |
✗ | not shown |
Generated by Syntax Scribe