Skip to content

⬅️ Back to Table of Contents

πŸ“„ prefer-destructuring

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 8
πŸ“¦ Imports 10
πŸ“Š Variables & Constants 2
πŸ“‘ Type Aliases 5

πŸ“š Table of Contents

πŸ› οΈ File Location:

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

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'prefer-destructuring'
meta.type 'suggestion'
meta.docs.description 'Require destructuring from arrays and/or objects'
meta.docs.extendsBaseRule true
meta.docs.frozen true
meta.docs.requiresTypeChecking true
meta.fixable baseRule.meta.fixable
meta.hasSuggestions baseRule.meta.hasSuggestions
meta.messages baseRule.meta.messages
defaultOptions [ { AssignmentExpression: { array: true, object: true, }, VariableDeclarator: { array: true, object: true, }, }, {}, ]

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
JSONSchema4 @typescript-eslint/utils/json-schema
AST_NODE_TYPES @typescript-eslint/utils
InferMessageIdsTypeFromRule ../util
InferOptionsTypeFromRule ../util
createRule ../util
getParserServices ../util
isTypeAnyType ../util
getESLintCoreRule ../util/getESLintCoreRule

Variables & Constants

Name Type Kind Value Exported
destructuringTypeConfig JSONSchema4 const { type: 'object', additionalProperties: false, properties: { array: { type: '... βœ—
schema readonly JSONSchema4[] const [ { oneOf: [ { type: 'object', additionalProperties: false, properties: { Ass... βœ—

Functions

create(context: any, [enabledTypes, options]: any): { AssignmentExpression(node: any): void; VariableDeclarator…

Parameters:

  • context any
  • [enabledTypes, options] any

Returns: { AssignmentExpression(node: any): void; VariableDeclarator(node: any): void; }

Calls:

  • getParserServices (from ../util)
  • program.getTypeChecker
  • baseRule.create
  • performCheck
  • baseRulesWithoutFix
  • isArrayLiteralIntegerIndexAccess
  • esTreeNodeToTSNodeMap.get
  • typeChecker.getTypeAtLocation
  • isTypeAnyOrIterableType
  • getNormalizedEnabledType
  • context.report
  • rules.AssignmentExpression
  • rules.VariableDeclarator
  • noFixContext
Code
create(context, [enabledTypes, options]) {
    const {
      enforceForDeclarationWithTypeAnnotation = false,
      enforceForRenamedProperties = false,
    } = options;
    const { esTreeNodeToTSNodeMap, program } = getParserServices(context);
    const typeChecker = program.getTypeChecker();
    const baseRules = baseRule.create(context);
    let baseRulesWithoutFixCache: typeof baseRules | null = null;

    return {
      AssignmentExpression(node): void {
        if (node.operator !== '=') {
          return;
        }
        performCheck(node.left, node.right, node);
      },
      VariableDeclarator(node): void {
        performCheck(node.id, node.init, node);
      },
    };

    function performCheck(
      leftNode: TSESTree.BindingName | TSESTree.Expression,
      rightNode: TSESTree.Expression | null,
      reportNode: TSESTree.AssignmentExpression | TSESTree.VariableDeclarator,
    ): void {
      const rules =
        leftNode.type === AST_NODE_TYPES.Identifier &&
        leftNode.typeAnnotation == null
          ? baseRules
          : baseRulesWithoutFix();
      if (
        (leftNode.type === AST_NODE_TYPES.ArrayPattern ||
          leftNode.type === AST_NODE_TYPES.Identifier ||
          leftNode.type === AST_NODE_TYPES.ObjectPattern) &&
        leftNode.typeAnnotation != null &&
        !enforceForDeclarationWithTypeAnnotation
      ) {
        return;
      }

      if (
        rightNode != null &&
        isArrayLiteralIntegerIndexAccess(rightNode) &&
        rightNode.object.type !== AST_NODE_TYPES.Super
      ) {
        const tsObj = esTreeNodeToTSNodeMap.get(rightNode.object);
        const objType = typeChecker.getTypeAtLocation(tsObj);
        if (!isTypeAnyOrIterableType(objType, typeChecker)) {
          if (
            !enforceForRenamedProperties ||
            !getNormalizedEnabledType(reportNode.type, 'object')
          ) {
            return;
          }
          context.report({
            node: reportNode,
            messageId: 'preferDestructuring',
            data: { type: 'object' },
          });
          return;
        }
      }

      if (reportNode.type === AST_NODE_TYPES.AssignmentExpression) {
        rules.AssignmentExpression(reportNode);
      } else {
        rules.VariableDeclarator(reportNode);
      }
    }

    function getNormalizedEnabledType(
      nodeType:
        AST_NODE_TYPES.AssignmentExpression | AST_NODE_TYPES.VariableDeclarator,
      destructuringType: 'array' | 'object',
    ): boolean | undefined {
      if ('object' in enabledTypes || 'array' in enabledTypes) {
        return enabledTypes[destructuringType];
      }
      return enabledTypes[nodeType as keyof typeof enabledTypes][
        destructuringType as keyof (typeof enabledTypes)[keyof typeof enabledTypes]
      ];
    }

    function baseRulesWithoutFix(): ReturnType<typeof baseRule.create> {
      baseRulesWithoutFixCache ??= baseRule.create(noFixContext(context));
      return baseRulesWithoutFixCache;
    }
  }

noFixContext(context: Context): Context

Parameters:

  • context Context

Returns: Context

Calls:

  • context.report
  • Reflect.get

Internal Comments:

// we can't directly proxy `context` because its `report` property is non-configurable
// and non-writable. So we proxy `customContext` and redirect all
// property access to the original context except for `report`

Code
function noFixContext(context: Context): Context {
  const customContext: {
    report: Context['report'];
  } = {
    report: (descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    },
  };

  // we can't directly proxy `context` because its `report` property is non-configurable
  // and non-writable. So we proxy `customContext` and redirect all
  // property access to the original context except for `report`
  return new Proxy(customContext as typeof context, {
    get(target, path, receiver): unknown {
      if (path !== 'report') {
        return Reflect.get(context, path, receiver);
      }
      return Reflect.get(target, path, receiver);
    },
  });
}

isTypeAnyOrIterableType(type: ts.Type, typeChecker: ts.TypeChecker): boolean

Parameters:

  • type ts.Type
  • typeChecker ts.TypeChecker

Returns: boolean

Calls:

  • isTypeAnyType (from ../util)
  • type.isUnion
  • tsutils.getWellKnownSymbolPropertyOfType
  • type.types.every
  • isTypeAnyOrIterableType
Code
function isTypeAnyOrIterableType(
  type: ts.Type,
  typeChecker: ts.TypeChecker,
): boolean {
  if (isTypeAnyType(type)) {
    return true;
  }
  if (!type.isUnion()) {
    const iterator = tsutils.getWellKnownSymbolPropertyOfType(
      type,
      'iterator',
      typeChecker,
    );
    return iterator != null;
  }
  return type.types.every(t => isTypeAnyOrIterableType(t, typeChecker));
}

isArrayLiteralIntegerIndexAccess(node: TSESTree.Expression): node is TSESTree.MemberExpression

Parameters:

  • node TSESTree.Expression

Returns: node is TSESTree.MemberExpression

Calls:

  • Number.isInteger
Code
function isArrayLiteralIntegerIndexAccess(
  node: TSESTree.Expression,
): node is TSESTree.MemberExpression {
  if (node.type !== AST_NODE_TYPES.MemberExpression) {
    return false;
  }
  if (node.property.type !== AST_NODE_TYPES.Literal) {
    return false;
  }
  return Number.isInteger(node.property.value);
}

Internal helpers

Declared inside another function in this file.

performCheck(leftNode: TSESTree.BindingName | TSESTree.Express…, rightNode: TSESTree.Expression | null, reportNode: TSESTree.AssignmentExpression | TSESTre…): void

Parameters:

  • leftNode TSESTree.BindingName | TSESTree.Expression
  • rightNode TSESTree.Expression | null
  • reportNode TSESTree.AssignmentExpression | TSESTree.VariableDeclarator

Returns: void

Calls:

  • baseRulesWithoutFix
  • isArrayLiteralIntegerIndexAccess
  • esTreeNodeToTSNodeMap.get
  • typeChecker.getTypeAtLocation
  • isTypeAnyOrIterableType
  • getNormalizedEnabledType
  • context.report
  • rules.AssignmentExpression
  • rules.VariableDeclarator
Code
function performCheck(
      leftNode: TSESTree.BindingName | TSESTree.Expression,
      rightNode: TSESTree.Expression | null,
      reportNode: TSESTree.AssignmentExpression | TSESTree.VariableDeclarator,
    ): void {
      const rules =
        leftNode.type === AST_NODE_TYPES.Identifier &&
        leftNode.typeAnnotation == null
          ? baseRules
          : baseRulesWithoutFix();
      if (
        (leftNode.type === AST_NODE_TYPES.ArrayPattern ||
          leftNode.type === AST_NODE_TYPES.Identifier ||
          leftNode.type === AST_NODE_TYPES.ObjectPattern) &&
        leftNode.typeAnnotation != null &&
        !enforceForDeclarationWithTypeAnnotation
      ) {
        return;
      }

      if (
        rightNode != null &&
        isArrayLiteralIntegerIndexAccess(rightNode) &&
        rightNode.object.type !== AST_NODE_TYPES.Super
      ) {
        const tsObj = esTreeNodeToTSNodeMap.get(rightNode.object);
        const objType = typeChecker.getTypeAtLocation(tsObj);
        if (!isTypeAnyOrIterableType(objType, typeChecker)) {
          if (
            !enforceForRenamedProperties ||
            !getNormalizedEnabledType(reportNode.type, 'object')
          ) {
            return;
          }
          context.report({
            node: reportNode,
            messageId: 'preferDestructuring',
            data: { type: 'object' },
          });
          return;
        }
      }

      if (reportNode.type === AST_NODE_TYPES.AssignmentExpression) {
        rules.AssignmentExpression(reportNode);
      } else {
        rules.VariableDeclarator(reportNode);
      }
    }

getNormalizedEnabledType(nodeType: AST_NODE_TYPES.AssignmentExpression | A…, destructuringType: 'array' | 'object'): boolean | undefined

Parameters:

  • nodeType AST_NODE_TYPES.AssignmentExpression | AST_NODE_TYPES.VariableDeclarator
  • destructuringType 'array' | 'object'

Returns: boolean | undefined

Code
function getNormalizedEnabledType(
      nodeType:
        AST_NODE_TYPES.AssignmentExpression | AST_NODE_TYPES.VariableDeclarator,
      destructuringType: 'array' | 'object',
    ): boolean | undefined {
      if ('object' in enabledTypes || 'array' in enabledTypes) {
        return enabledTypes[destructuringType];
      }
      return enabledTypes[nodeType as keyof typeof enabledTypes][
        destructuringType as keyof (typeof enabledTypes)[keyof typeof enabledTypes]
      ];
    }

baseRulesWithoutFix(): ReturnType<typeof baseRule.create>

Returns: ReturnType<typeof baseRule.create>

Calls:

  • baseRule.create
  • noFixContext
Code
function baseRulesWithoutFix(): ReturnType<typeof baseRule.create> {
      baseRulesWithoutFixCache ??= baseRule.create(noFixContext(context));
      return baseRulesWithoutFixCache;
    }

report(descriptor: any): void

Parameters:

  • descriptor any

Returns: void

Calls:

  • context.report
Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }

Type Aliases

BaseOptions

type BaseOptions = InferOptionsTypeFromRule<typeof baseRule>;

EnforcementOptions

type EnforcementOptions = {
  enforceForDeclarationWithTypeAnnotation?: boolean;
} & BaseOptions[1];

Options

type Options = [BaseOptions[0], EnforcementOptions];

MessageIds

type MessageIds = InferMessageIdsTypeFromRule<typeof baseRule>;

Context

type Context = TSESLint.RuleContext<MessageIds, Options>;

Generated by Syntax Scribe