Skip to content

⬅️ Back to Table of Contents

πŸ“„ prefer-for-of

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 9
πŸ“¦ Imports 5

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/prefer-for-of.ts

πŸ“€ Default Export

export default createRule({ ... })
Property Value
name 'prefer-for-of'
meta.type 'suggestion'
meta.docs.description 'Enforce the use of for-of loop over the standard for loop where possible'
meta.docs.recommended 'stylistic'
meta.messages.preferForOf 'Expected a for-of loop instead of a for loop with this simple iteration.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
isAssignee ../util

Functions

create(context: any): { 'ForStatement:exit'(node: TSESTree.ForStatement): void; }

Parameters:

  • context any

Returns: { 'ForStatement:exit'(node: TSESTree.ForStatement): void; }

Calls:

  • isLiteral
  • isMatchingIdentifier
  • context.sourceCode.getText
  • indexVar.references.every
  • contains
  • isAssignee (from ../util)
  • isSingleVariableDeclaration
  • isZeroInitialized
  • isLessThanLengthExpression
  • context.sourceCode.getDeclaredVariables
  • isIncrement
  • isIndexOnlyUsedWithArray
  • context.report

Internal Comments:

// x++ or ++x
// x += 1
// x = x + 1 or x = 1 + x (x2)

Code
create(context) {
    function isSingleVariableDeclaration(
      node: TSESTree.Node | null,
    ): node is TSESTree.VariableDeclaration {
      return (
        node?.type === AST_NODE_TYPES.VariableDeclaration &&
        node.kind !== 'const' &&
        node.declarations.length === 1
      );
    }

    function isLiteral(
      node: TSESTree.Expression | TSESTree.PrivateIdentifier,
      value: number,
    ): boolean {
      return node.type === AST_NODE_TYPES.Literal && node.value === value;
    }

    function isZeroInitialized(node: TSESTree.VariableDeclarator): boolean {
      return node.init != null && isLiteral(node.init, 0);
    }

    function isMatchingIdentifier(
      node: TSESTree.Expression | TSESTree.PrivateIdentifier,
      name: string,
    ): boolean {
      return node.type === AST_NODE_TYPES.Identifier && node.name === name;
    }

    function isLessThanLengthExpression(
      node: TSESTree.Node | null,
      name: string,
    ): TSESTree.Expression | null {
      if (
        node?.type === AST_NODE_TYPES.BinaryExpression &&
        node.operator === '<' &&
        isMatchingIdentifier(node.left, name) &&
        node.right.type === AST_NODE_TYPES.MemberExpression &&
        isMatchingIdentifier(node.right.property, 'length')
      ) {
        return node.right.object;
      }
      return null;
    }

    function isIncrement(node: TSESTree.Node | null, name: string): boolean {
      if (!node) {
        return false;
      }

      switch (node.type) {
        case AST_NODE_TYPES.UpdateExpression:
          // x++ or ++x
          return (
            node.operator === '++' && isMatchingIdentifier(node.argument, name)
          );
        case AST_NODE_TYPES.AssignmentExpression:
          if (isMatchingIdentifier(node.left, name)) {
            if (node.operator === '+=') {
              // x += 1
              return isLiteral(node.right, 1);
            }
            if (node.operator === '=') {
              // x = x + 1 or x = 1 + x
              const expr = node.right;
              return (
                expr.type === AST_NODE_TYPES.BinaryExpression &&
                expr.operator === '+' &&
                ((isMatchingIdentifier(expr.left, name) &&
                  isLiteral(expr.right, 1)) ||
                  (isLiteral(expr.left, 1) &&
                    isMatchingIdentifier(expr.right, name)))
              );
            }
          }
      }
      return false;
    }

    function contains(outer: TSESTree.Node, inner: TSESTree.Node): boolean {
      return (
        outer.range[0] <= inner.range[0] && outer.range[1] >= inner.range[1]
      );
    }

    function isIndexOnlyUsedWithArray(
      body: TSESTree.Statement,
      indexVar: TSESLint.Scope.Variable,
      arrayExpression: TSESTree.Expression,
    ): boolean {
      const arrayText = context.sourceCode.getText(arrayExpression);
      return indexVar.references.every(reference => {
        const id = reference.identifier;
        const node = id.parent;
        return (
          !contains(body, id) ||
          (node.type === AST_NODE_TYPES.MemberExpression &&
            node.object.type !== AST_NODE_TYPES.ThisExpression &&
            node.property === id &&
            context.sourceCode.getText(node.object) === arrayText &&
            !isAssignee(node))
        );
      });
    }

    return {
      'ForStatement:exit'(node: TSESTree.ForStatement): void {
        if (!isSingleVariableDeclaration(node.init)) {
          return;
        }

        const declarator = node.init.declarations[0] as
          TSESTree.VariableDeclarator | undefined;
        if (
          !declarator ||
          !isZeroInitialized(declarator) ||
          declarator.id.type !== AST_NODE_TYPES.Identifier
        ) {
          return;
        }

        const indexName = declarator.id.name;
        const arrayExpression = isLessThanLengthExpression(
          node.test,
          indexName,
        );
        if (!arrayExpression) {
          return;
        }

        const [indexVar] = context.sourceCode.getDeclaredVariables(node.init);
        if (
          isIncrement(node.update, indexName) &&
          isIndexOnlyUsedWithArray(node.body, indexVar, arrayExpression)
        ) {
          context.report({
            node,
            messageId: 'preferForOf',
          });
        }
      },
    };
  }

Internal helpers

Declared inside another function in this file.

isSingleVariableDeclaration(node: TSESTree.Node | null): node is TSESTree.VariableDeclaration

Parameters:

  • node TSESTree.Node | null

Returns: node is TSESTree.VariableDeclaration

Code
function isSingleVariableDeclaration(
      node: TSESTree.Node | null,
    ): node is TSESTree.VariableDeclaration {
      return (
        node?.type === AST_NODE_TYPES.VariableDeclaration &&
        node.kind !== 'const' &&
        node.declarations.length === 1
      );
    }

isLiteral(node: TSESTree.Expression | TSESTree.PrivateI…, value: number): boolean

Parameters:

  • node TSESTree.Expression | TSESTree.PrivateIdentifier
  • value number

Returns: boolean

Code
function isLiteral(
      node: TSESTree.Expression | TSESTree.PrivateIdentifier,
      value: number,
    ): boolean {
      return node.type === AST_NODE_TYPES.Literal && node.value === value;
    }

isZeroInitialized(node: TSESTree.VariableDeclarator): boolean

Parameters:

  • node TSESTree.VariableDeclarator

Returns: boolean

Calls:

  • isLiteral
Code
function isZeroInitialized(node: TSESTree.VariableDeclarator): boolean {
      return node.init != null && isLiteral(node.init, 0);
    }

isMatchingIdentifier(node: TSESTree.Expression | TSESTree.PrivateI…, name: string): boolean

Parameters:

  • node TSESTree.Expression | TSESTree.PrivateIdentifier
  • name string

Returns: boolean

Code
function isMatchingIdentifier(
      node: TSESTree.Expression | TSESTree.PrivateIdentifier,
      name: string,
    ): boolean {
      return node.type === AST_NODE_TYPES.Identifier && node.name === name;
    }

isLessThanLengthExpression(node: TSESTree.Node | null, name: string): TSESTree.Expression | null

Parameters:

  • node TSESTree.Node | null
  • name string

Returns: TSESTree.Expression | null

Calls:

  • isMatchingIdentifier
Code
function isLessThanLengthExpression(
      node: TSESTree.Node | null,
      name: string,
    ): TSESTree.Expression | null {
      if (
        node?.type === AST_NODE_TYPES.BinaryExpression &&
        node.operator === '<' &&
        isMatchingIdentifier(node.left, name) &&
        node.right.type === AST_NODE_TYPES.MemberExpression &&
        isMatchingIdentifier(node.right.property, 'length')
      ) {
        return node.right.object;
      }
      return null;
    }

isIncrement(node: TSESTree.Node | null, name: string): boolean

Parameters:

  • node TSESTree.Node | null
  • name string

Returns: boolean

Calls:

  • isMatchingIdentifier
  • isLiteral

Internal Comments:

// x++ or ++x
// x += 1
// x = x + 1 or x = 1 + x (x2)

Code
function isIncrement(node: TSESTree.Node | null, name: string): boolean {
      if (!node) {
        return false;
      }

      switch (node.type) {
        case AST_NODE_TYPES.UpdateExpression:
          // x++ or ++x
          return (
            node.operator === '++' && isMatchingIdentifier(node.argument, name)
          );
        case AST_NODE_TYPES.AssignmentExpression:
          if (isMatchingIdentifier(node.left, name)) {
            if (node.operator === '+=') {
              // x += 1
              return isLiteral(node.right, 1);
            }
            if (node.operator === '=') {
              // x = x + 1 or x = 1 + x
              const expr = node.right;
              return (
                expr.type === AST_NODE_TYPES.BinaryExpression &&
                expr.operator === '+' &&
                ((isMatchingIdentifier(expr.left, name) &&
                  isLiteral(expr.right, 1)) ||
                  (isLiteral(expr.left, 1) &&
                    isMatchingIdentifier(expr.right, name)))
              );
            }
          }
      }
      return false;
    }

contains(outer: TSESTree.Node, inner: TSESTree.Node): boolean

Parameters:

  • outer TSESTree.Node
  • inner TSESTree.Node

Returns: boolean

Code
function contains(outer: TSESTree.Node, inner: TSESTree.Node): boolean {
      return (
        outer.range[0] <= inner.range[0] && outer.range[1] >= inner.range[1]
      );
    }

isIndexOnlyUsedWithArray(body: TSESTree.Statement, indexVar: TSESLint.Scope.Variable, arrayExpression: TSESTree.Expression): boolean

Parameters:

  • body TSESTree.Statement
  • indexVar TSESLint.Scope.Variable
  • arrayExpression TSESTree.Expression

Returns: boolean

Calls:

  • context.sourceCode.getText
  • indexVar.references.every
  • contains
  • isAssignee (from ../util)
Code
function isIndexOnlyUsedWithArray(
      body: TSESTree.Statement,
      indexVar: TSESLint.Scope.Variable,
      arrayExpression: TSESTree.Expression,
    ): boolean {
      const arrayText = context.sourceCode.getText(arrayExpression);
      return indexVar.references.every(reference => {
        const id = reference.identifier;
        const node = id.parent;
        return (
          !contains(body, id) ||
          (node.type === AST_NODE_TYPES.MemberExpression &&
            node.object.type !== AST_NODE_TYPES.ThisExpression &&
            node.property === id &&
            context.sourceCode.getText(node.object) === arrayText &&
            !isAssignee(node))
        );
      });
    }

Generated by Syntax Scribe