Skip to content

⬅️ Back to Table of Contents

📄 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.collectUnusedVariables
  • ESLintUtils.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:

  • program TSESTree.Program
  • scopeManager ScopeManager

Returns: VariableAnalysis

Calls:

  • this.RESULTS_CACHE.get
  • visitor.visit
  • visitor.collectUnusedVariables
  • visitor.getScope
  • this.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:

  • node TSESTree.Identifier

Returns: void

Calls:

  • this.getScope
  • scope.block.params.includes
  • this.markVariableAsUsed

Internal Comments:

// this parameters should always be considered used as they're pseudo-parameters (x2)

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:

  • node TSESTree.TSEnumDeclaration

Returns: void

Calls:

  • this.getScope
  • this.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:

  • node TSESTree.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
protected TSMappedType(node: TSESTree.TSMappedType): void {
    // mapped types create a variable for their type name, but it's not necessary to reference it,
    // so we shouldn't consider it as unused for the purpose of this rule.
    this.markVariableAsUsed(node.key);
  }

UnusedVarsVisitor.TSModuleDeclaration(node: TSESTree.TSModuleDeclaration): void

Parameters:

  • node TSESTree.TSModuleDeclaration

Returns: void

Calls:

  • this.markVariableAsUsed

Internal Comments:

// -- global augmentation can be in any file, and they do not need exports

Code
protected TSModuleDeclaration(node: TSESTree.TSModuleDeclaration): void {
    // -- global augmentation can be in any file, and they do not need exports
    if (node.kind === 'global') {
      this.markVariableAsUsed('global', node.parent);
    }
  }

UnusedVarsVisitor.TSParameterProperty(node: TSESTree.TSParameterProperty): void

Parameters:

  • node TSESTree.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:

  • currentNode TSESTree.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:

  • variableOrIdentifier ScopeVariable | TSESTree.Identifier

Returns: void

Code
private markVariableAsUsed(
    variableOrIdentifier: ScopeVariable | TSESTree.Identifier,
  ): void;

UnusedVarsVisitor.visitClass(node: TSESTree.ClassDeclaration | TSESTree.Cl…): void

Parameters:

  • node TSESTree.ClassDeclaration | TSESTree.ClassExpression

Returns: void

Calls:

  • this.getScope
  • this.markVariableAsUsed

Internal Comments:

// skip a variable of class itself name in the class scope (x2)

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:

  • node TSESTree.ForInStatement | TSESTree.ForOfStatement

Returns: void

Calls:

  • this.#scopeManager.getDeclaredVariables(node.left).at
  • this.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:

  • node TSESTree.FunctionDeclaration | TSESTree.FunctionExpression

Returns: void

Calls:

  • this.getScope
  • scope.set.get
  • this.markVariableAsUsed

Internal Comments:

// skip implicit "arguments" variable (x2)

Code
private visitFunction(
    node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression,
  ): void {
    const scope = this.getScope(node);
    // skip implicit "arguments" variable
    const variable = scope.set.get('arguments');
    if (variable?.defs.length === 0) {
      this.markVariableAsUsed(variable);
    }
  }

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.visitPattern
  • this.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:

  • node TSESTree.MethodDefinition | TSESTree.Property

Returns: void

Calls:

  • this.visitPattern
  • this.markVariableAsUsed

Internal Comments:

// ignore setter parameters because they're syntactically required to exist

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:

  • inner any: A node which is expected as inside.
  • outer any: A node which is expected as outside.

Returns: undefined true if the inner node exists in the outer node.

Raw JSDoc
/**
 * Checks the position of given nodes.
 * @param inner A node which is expected as inside.
 * @param outer A node which is expected as outside.
 * @returns `true` if the `inner` node exists in the `outer` node.
 */
Code
function isInside(inner: TSESTree.Node, outer: TSESTree.Node): boolean {
  return inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];
}

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:

  • ref any: The reference to check.
  • nodes any: 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
function isSelfReference(
  ref: TSESLint.Scope.Reference,
  nodes: Set<TSESTree.Node>,
): boolean {
  let scope: TSESLint.Scope.Scope | null = ref.from;

  while (scope) {
    if (nodes.has(scope.block)) {
      return true;
    }

    scope = scope.upper;
  }

  return false;
}

isMergeableExported(variable: ScopeVariable): boolean

Determine if the variable is directly exported

Parameters:

  • variable any: the variable to check
Raw JSDoc
/**
 * Determine if the variable is directly exported
 * @param variable the variable to check
 */

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:

  • variable any: eslint-scope variable object.

Returns: undefined True if the variable is exported, false if not.

Raw JSDoc
/**
 * Determines if a given variable is being exported from a module.
 * @param variable eslint-scope variable object.
 * @returns True if the variable is exported, false if not.
 */

Calls:

  • variable.defs.some
  • node.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:

  • variable any: The variable to check.

Returns: undefined True if the variable is used

Raw JSDoc
/**
 * Determines if the variable is used.
 * @param variable The variable to check.
 * @returns True if the variable is used
 */

Calls:

  • variable.defs.forEach
  • functionDefinitions.add
  • nodes.add
  • isInside
  • isUnusedExpression
  • ASTUtils.isFunction
  • ASTUtils.isLoop
  • isInLoop
  • parent.type.endsWith
  • getUpperFunction
  • isStorableFunction
  • ref.isRead
  • LOGICAL_ASSIGNMENT_OPERATORS.has
  • isInsideOfStorableFunction
  • getFunctionDefinitions
  • getTypeDeclarations
  • getModuleDeclarations
  • getEnumDeclarations
  • variable.defs.every
  • variable.references.some
  • isReadForItself
  • getRhsNode
  • referenceContainsTypeQuery (from ./referenceContainsTypeQuery)
  • referenceContainsTypePredicate (from ./referenceContainsTypePredicate)
  • isSelfReference
  • isInsideOneOf

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:

  • variable any: eslint-scope variable object.

Returns: undefined Function nodes.

Raw JSDoc
/**
   * Gets a list of function definitions for a specified variable.
   * @param variable eslint-scope variable object.
   * @returns Function nodes.
   */

Calls:

  • variable.defs.forEach
  • functionDefinitions.add

Internal Comments:

// FunctionDeclarations
// FunctionExpressions

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:

  • variable ScopeVariable

Returns: Set<TSESTree.Node>

Calls:

  • variable.defs.forEach
  • nodes.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:

  • variable ScopeVariable

Returns: Set<TSESTree.Node>

Calls:

  • variable.defs.forEach
  • nodes.add
Code
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;
  }

getEnumDeclarations(variable: ScopeVariable): Set<TSESTree.Node>

Parameters:

  • variable ScopeVariable

Returns: Set<TSESTree.Node>

Calls:

  • variable.defs.forEach
  • nodes.add
Code
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;
  }

isInsideOneOf(ref: TSESLint.Scope.Reference, nodes: Set<TSESTree.Node>): boolean

Checks if the ref is contained within one of the given nodes

Raw JSDoc
/**
   * Checks if the ref is contained within one of the given nodes
   */

Calls:

  • isInside
Code
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;
  }

isUnusedExpression(node: TSESTree.Expression): boolean

Checks whether a given node is unused expression or not.

Parameters:

  • node any: The node itself

Returns: undefined The node is an unused expression.

Raw JSDoc
/**
   * Checks whether a given node is unused expression or not.
   * @param node The node itself
   * @returns The node is an unused expression.
   */

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:

  • ref any: A reference to check.
  • prevRhsNode any: 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.isFunction
  • ASTUtils.isLoop
  • isInLoop
  • isInside
  • isUnusedExpression

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:

  • node any: The node to check.

Returns: undefined true if the node is in a loop.

Raw JSDoc
/**
     * 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.
     */

Calls:

  • ASTUtils.isFunction
  • ASTUtils.isLoop
Code
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;
    }

isReadForItself(ref: TSESLint.Scope.Reference, rhsNode: TSESTree.Node | null): boolean

Checks whether a given reference is a read to update itself or not.

Parameters:

  • ref any: A reference to check.
  • rhsNode any: The RHS node of the previous assignment.

Returns: undefined The reference is a read to update itself.

Raw JSDoc
/**
   * 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.
   */

Calls:

  • ASTUtils.isFunction
  • isInside
  • parent.type.endsWith
  • getUpperFunction
  • isStorableFunction
  • ref.isRead
  • LOGICAL_ASSIGNMENT_OPERATORS.has
  • isUnusedExpression
  • isInsideOfStorableFunction

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:

  • id any: An Identifier node to check.
  • rhsNode any: 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.isFunction
  • isInside
  • parent.type.endsWith
  • getUpperFunction
  • isStorableFunction

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:

  • node any: A start node to find.

Returns: undefined A found function node.

Raw JSDoc
/**
       * Finds a function node from ancestors of a node.
       * @param node A start node to find.
       * @returns A found function node.
       */

Calls:

  • ASTUtils.isFunction
Code
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;
      }

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:

  • funcNode any: A function node to check.
  • rhsNode any: 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:

  • isInside
  • parent.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
interface VariableAnalysis {
  readonly unusedVariables: ReadonlySet<ScopeVariable>;
  readonly usedVariables: ReadonlySet<ScopeVariable>;
}

Properties

Name Type Optional Description
unusedVariables ReadonlySet<ScopeVariable> not shown
usedVariables ReadonlySet<ScopeVariable> not shown

MutableVariableAnalysis

Interface Code
interface MutableVariableAnalysis {
  readonly unusedVariables: Set<ScopeVariable>;
  readonly usedVariables: Set<ScopeVariable>;
}

Properties

Name Type Optional Description
unusedVariables Set<ScopeVariable> not shown
usedVariables Set<ScopeVariable> not shown

Generated by Syntax Scribe