Skip to content

⬅️ Back to Table of Contents

📄 prefer-destructuring.ts

📊 Analysis Summary

Metric Count
🔧 Functions 12
📦 Imports 10
📊 Variables & Constants 5
📑 Type Aliases 5

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/prefer-destructuring.ts

📦 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: 'boolean',
},
object: {
type: 'boolean',
},
},
}`
schema readonly JSONSchema4[] const `[
{
oneOf: [
{
type: 'object',
additionalProperties: false,
properties: {
AssignmentExpression: destructuringTypeConfig,
VariableDeclarator: destructuringTypeConfig,
},
},
destructuringTypeConfig,
],
},
{
type: 'object',
properties: {
enforceForDeclarationWithTypeAnnotation: {
type: 'boolean',
description:
'Whether to enforce destructuring on variable declarations with type annotations.',
},
enforceForRenamedProperties: {
type: 'boolean',
description:
'Whether to enforce destructuring that use a different variable name than the property name.',
},
},
},
]`
baseRulesWithoutFixCache typeof baseRules | null let/var null
rules any const `leftNode.type === AST_NODE_TYPES.Identifier &&
leftNode.typeAnnotation == null
? baseRules
: baseRulesWithoutFix()`
customContext `{
report: Context['report'];
}| const |{
report: (descriptor): void => {
context.report({
...descriptor,
fix: undefined,
});
},
}`

Functions

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

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);
      }
    }
  • Parameters:
  • leftNode: TSESTree.BindingName | TSESTree.Expression
  • rightNode: TSESTree.Expression | null
  • reportNode: TSESTree.AssignmentExpression | TSESTree.VariableDeclarator
  • Return Type: void
  • Calls:
  • baseRulesWithoutFix
  • isArrayLiteralIntegerIndexAccess
  • esTreeNodeToTSNodeMap.get
  • typeChecker.getTypeAtLocation
  • isTypeAnyOrIterableType
  • getNormalizedEnabledType
  • context.report
  • rules.AssignmentExpression
  • rules.VariableDeclarator

`getNormalizedEnabledType(nodeType: | AST_NODE_TYPES.AssignmentExpression

    | AST_NODE_TYPES.VariableDeclarator, destructuringType: 'array' | 'object'): 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]
      ];
    }
  • Parameters:
  • nodeType: | AST_NODE_TYPES.AssignmentExpression | AST_NODE_TYPES.VariableDeclarator
  • destructuringType: 'array' | 'object'
  • Return Type: boolean | undefined

baseRulesWithoutFix(): ReturnType<typeof baseRule.create>

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

noFixContext(context: Context): Context

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<Context>(customContext as typeof context, {
    get(target, path, receiver): unknown {
      if (path !== 'report') {
        return Reflect.get(context, path, receiver);
      }
      return Reflect.get(target, path, receiver);
    },
  });
}
  • Parameters:
  • context: Context
  • Return Type: 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`
    

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

report(descriptor: any): void

Code
(descriptor): void => {
      context.report({
        ...descriptor,
        fix: undefined,
      });
    }
  • Parameters:
  • descriptor: any
  • Return Type: void
  • Calls:
  • context.report

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

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));
}
  • Parameters:
  • type: ts.Type
  • typeChecker: ts.TypeChecker
  • Return Type: boolean
  • Calls:
  • isTypeAnyType (from ../util)
  • type.isUnion
  • tsutils.getWellKnownSymbolPropertyOfType
  • type.types.every
  • isTypeAnyOrIterableType

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

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);
}
  • Parameters:
  • node: TSESTree.Expression
  • Return Type: node is TSESTree.MemberExpression
  • Calls:
  • Number.isInteger

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>;