Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-unnecessary-parameter-property-assignment

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 10
πŸ“¦ Imports 7
πŸ“Š Variables & Constants 1

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts

πŸ“€ Default Export

export default createRule({ ... })
Property Value
name 'no-unnecessary-parameter-property-assignment'
meta.type 'suggestion'
meta.docs.description 'Disallow unnecessary assignment of constructor property parameter'
meta.messages.unnecessaryAssign 'This assignment is unnecessary since it is already assigned by a parameter property.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESTree @typescript-eslint/utils
DefinitionType @typescript-eslint/scope-manager
AST_NODE_TYPES @typescript-eslint/utils
ASTUtils @typescript-eslint/utils
createRule ../util
getStaticStringValue ../util
nullThrows ../util

Variables & Constants

Name Type Kind Value Exported
UNNECESSARY_OPERATORS Set<string> const new Set(['??=', '&&=', '=', '\|\|=']) βœ—

Functions

create(context: any): { ClassBody(): void; 'ClassBody:exit'(): void; "MethodDefin…

Parameters:

  • context any

Returns: { ClassBody(): void; 'ClassBody:exit'(): void; "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"(node: TSESTree.AssignmentExpression): void; 'PropertyDefinition AssignmentExpression'(node: TSESTree.AssignmentExpression): void; }

Calls:

  • isThisMemberExpression
  • getStaticStringValue (from ../util)
  • findParentFunction
  • findParentPropertyDefinition
  • ASTUtils.isConstructor
  • context.sourceCode.getScope
  • scope.references.find
  • rightRef?.resolved?.defs.at
  • getIdentifier
  • reportInfoStack.push
  • nullThrows (from ../util)
  • reportInfoStack.pop
  • unnecessaryAssignments.forEach
  • assignedBeforeConstructor.has
  • context.report
  • getPropertyName
  • isArrowIIFE
  • isConstructorFunctionExpression
  • reportInfoStack.at
  • UNNECESSARY_OPERATORS.has
  • assignedBeforeUnnecessary.add
  • isReferenceFromParameter
  • functionNode.params.some
  • isParameterPropertyWithName
  • assignedBeforeUnnecessary.has
  • unnecessaryAssignments.push
  • assignedBeforeConstructor.add
Code
create(context) {
    const reportInfoStack: {
      assignedBeforeConstructor: Set<string>;
      assignedBeforeUnnecessary: Set<string>;
      unnecessaryAssignments: {
        name: string;
        node: TSESTree.AssignmentExpression;
      }[];
    }[] = [];

    function isThisMemberExpression(
      node: TSESTree.Node,
    ): node is TSESTree.MemberExpression {
      return (
        node.type === AST_NODE_TYPES.MemberExpression &&
        node.object.type === AST_NODE_TYPES.ThisExpression
      );
    }

    function getPropertyName(node: TSESTree.Node): string | null {
      if (!isThisMemberExpression(node)) {
        return null;
      }

      if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
        return node.property.name;
      }
      if (node.computed) {
        return getStaticStringValue(node.property);
      }
      return null;
    }

    function findParentFunction(
      node: TSESTree.Node | undefined,
    ):
      | TSESTree.ArrowFunctionExpression
      | TSESTree.FunctionDeclaration
      | TSESTree.FunctionExpression
      | undefined {
      if (
        !node ||
        node.type === AST_NODE_TYPES.FunctionDeclaration ||
        node.type === AST_NODE_TYPES.FunctionExpression ||
        node.type === AST_NODE_TYPES.ArrowFunctionExpression
      ) {
        return node;
      }
      return findParentFunction(node.parent);
    }

    function findParentPropertyDefinition(
      node: TSESTree.Node | undefined,
    ): TSESTree.PropertyDefinition | undefined {
      if (!node || node.type === AST_NODE_TYPES.PropertyDefinition) {
        return node;
      }
      return findParentPropertyDefinition(node.parent);
    }

    function isConstructorFunctionExpression(
      node: TSESTree.Node | undefined,
    ): node is TSESTree.FunctionExpression {
      return (
        node?.type === AST_NODE_TYPES.FunctionExpression &&
        ASTUtils.isConstructor(node.parent)
      );
    }

    function isReferenceFromParameter(node: TSESTree.Identifier): boolean {
      const scope = context.sourceCode.getScope(node);

      const rightRef = scope.references.find(
        ref => ref.identifier.name === node.name,
      );
      return rightRef?.resolved?.defs.at(0)?.type === DefinitionType.Parameter;
    }

    function isParameterPropertyWithName(
      node: TSESTree.Parameter,
      name: string,
    ): boolean {
      return (
        node.type === AST_NODE_TYPES.TSParameterProperty &&
        ((node.parameter.type === AST_NODE_TYPES.Identifier && // constructor (public foo) {}
          node.parameter.name === name) ||
          (node.parameter.type === AST_NODE_TYPES.AssignmentPattern && // constructor (public foo = 1) {}
            node.parameter.left.name === name))
      );
    }

    function getIdentifier(node: TSESTree.Node): TSESTree.Identifier | null {
      if (node.type === AST_NODE_TYPES.Identifier) {
        return node;
      }
      if (
        node.type === AST_NODE_TYPES.TSAsExpression ||
        node.type === AST_NODE_TYPES.TSNonNullExpression
      ) {
        return getIdentifier(node.expression);
      }
      return null;
    }

    function isArrowIIFE(node: TSESTree.Node): boolean {
      return (
        node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
        node.parent.type === AST_NODE_TYPES.CallExpression
      );
    }

    return {
      ClassBody(): void {
        reportInfoStack.push({
          assignedBeforeConstructor: new Set(),
          assignedBeforeUnnecessary: new Set(),
          unnecessaryAssignments: [],
        });
      },
      'ClassBody:exit'(): void {
        const { assignedBeforeConstructor, unnecessaryAssignments } =
          nullThrows(reportInfoStack.pop(), 'The top stack should exist');
        unnecessaryAssignments.forEach(({ name, node }) => {
          if (assignedBeforeConstructor.has(name)) {
            return;
          }
          context.report({
            node,
            messageId: 'unnecessaryAssign',
          });
        });
      },
      "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"(
        node: TSESTree.AssignmentExpression,
      ): void {
        const leftName = getPropertyName(node.left);

        if (!leftName) {
          return;
        }

        let functionNode = findParentFunction(node);
        if (functionNode && isArrowIIFE(functionNode)) {
          functionNode = findParentFunction(functionNode.parent);
        }

        if (!isConstructorFunctionExpression(functionNode)) {
          return;
        }

        const { assignedBeforeUnnecessary, unnecessaryAssignments } =
          nullThrows(
            reportInfoStack.at(reportInfoStack.length - 1),
            'The top of stack should exist',
          );

        if (!UNNECESSARY_OPERATORS.has(node.operator)) {
          assignedBeforeUnnecessary.add(leftName);
          return;
        }

        const rightId = getIdentifier(node.right);

        if (leftName !== rightId?.name || !isReferenceFromParameter(rightId)) {
          return;
        }

        const hasParameterProperty = functionNode.params.some(param =>
          isParameterPropertyWithName(param, rightId.name),
        );

        if (hasParameterProperty && !assignedBeforeUnnecessary.has(leftName)) {
          unnecessaryAssignments.push({
            name: leftName,
            node,
          });
        }
      },
      'PropertyDefinition AssignmentExpression'(
        node: TSESTree.AssignmentExpression,
      ): void {
        const name = getPropertyName(node.left);

        if (!name) {
          return;
        }

        const functionNode = findParentFunction(node);
        if (
          functionNode &&
          !(
            isArrowIIFE(functionNode) &&
            findParentPropertyDefinition(node)?.value === functionNode.parent
          )
        ) {
          return;
        }

        const { assignedBeforeConstructor } = nullThrows(
          reportInfoStack.at(-1),
          'The top stack should exist',
        );
        assignedBeforeConstructor.add(name);
      },
    };
  }

Internal helpers

Declared inside another function in this file.

isThisMemberExpression(node: TSESTree.Node): node is TSESTree.MemberExpression

Parameters:

  • node TSESTree.Node

Returns: node is TSESTree.MemberExpression

Code
function isThisMemberExpression(
      node: TSESTree.Node,
    ): node is TSESTree.MemberExpression {
      return (
        node.type === AST_NODE_TYPES.MemberExpression &&
        node.object.type === AST_NODE_TYPES.ThisExpression
      );
    }

getPropertyName(node: TSESTree.Node): string | null

Parameters:

  • node TSESTree.Node

Returns: string | null

Calls:

  • isThisMemberExpression
  • getStaticStringValue (from ../util)
Code
function getPropertyName(node: TSESTree.Node): string | null {
      if (!isThisMemberExpression(node)) {
        return null;
      }

      if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
        return node.property.name;
      }
      if (node.computed) {
        return getStaticStringValue(node.property);
      }
      return null;
    }

findParentFunction(node: TSESTree.Node | undefined): | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDecla…

Parameters:

  • node TSESTree.Node | undefined

Returns: | TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | undefined

Calls:

  • findParentFunction
Code
function findParentFunction(
      node: TSESTree.Node | undefined,
    ):
      | TSESTree.ArrowFunctionExpression
      | TSESTree.FunctionDeclaration
      | TSESTree.FunctionExpression
      | undefined {
      if (
        !node ||
        node.type === AST_NODE_TYPES.FunctionDeclaration ||
        node.type === AST_NODE_TYPES.FunctionExpression ||
        node.type === AST_NODE_TYPES.ArrowFunctionExpression
      ) {
        return node;
      }
      return findParentFunction(node.parent);
    }

findParentPropertyDefinition(node: TSESTree.Node | undefined): TSESTree.PropertyDefinition | undefined

Parameters:

  • node TSESTree.Node | undefined

Returns: TSESTree.PropertyDefinition | undefined

Calls:

  • findParentPropertyDefinition
Code
function findParentPropertyDefinition(
      node: TSESTree.Node | undefined,
    ): TSESTree.PropertyDefinition | undefined {
      if (!node || node.type === AST_NODE_TYPES.PropertyDefinition) {
        return node;
      }
      return findParentPropertyDefinition(node.parent);
    }

isConstructorFunctionExpression(node: TSESTree.Node | undefined): node is TSESTree.FunctionExpression

Parameters:

  • node TSESTree.Node | undefined

Returns: node is TSESTree.FunctionExpression

Calls:

  • ASTUtils.isConstructor
Code
function isConstructorFunctionExpression(
      node: TSESTree.Node | undefined,
    ): node is TSESTree.FunctionExpression {
      return (
        node?.type === AST_NODE_TYPES.FunctionExpression &&
        ASTUtils.isConstructor(node.parent)
      );
    }

isReferenceFromParameter(node: TSESTree.Identifier): boolean

Parameters:

  • node TSESTree.Identifier

Returns: boolean

Calls:

  • context.sourceCode.getScope
  • scope.references.find
  • rightRef?.resolved?.defs.at
Code
function isReferenceFromParameter(node: TSESTree.Identifier): boolean {
      const scope = context.sourceCode.getScope(node);

      const rightRef = scope.references.find(
        ref => ref.identifier.name === node.name,
      );
      return rightRef?.resolved?.defs.at(0)?.type === DefinitionType.Parameter;
    }

isParameterPropertyWithName(node: TSESTree.Parameter, name: string): boolean

Parameters:

  • node TSESTree.Parameter
  • name string

Returns: boolean

Code
function isParameterPropertyWithName(
      node: TSESTree.Parameter,
      name: string,
    ): boolean {
      return (
        node.type === AST_NODE_TYPES.TSParameterProperty &&
        ((node.parameter.type === AST_NODE_TYPES.Identifier && // constructor (public foo) {}
          node.parameter.name === name) ||
          (node.parameter.type === AST_NODE_TYPES.AssignmentPattern && // constructor (public foo = 1) {}
            node.parameter.left.name === name))
      );
    }

getIdentifier(node: TSESTree.Node): TSESTree.Identifier | null

Parameters:

  • node TSESTree.Node

Returns: TSESTree.Identifier | null

Calls:

  • getIdentifier
Code
function getIdentifier(node: TSESTree.Node): TSESTree.Identifier | null {
      if (node.type === AST_NODE_TYPES.Identifier) {
        return node;
      }
      if (
        node.type === AST_NODE_TYPES.TSAsExpression ||
        node.type === AST_NODE_TYPES.TSNonNullExpression
      ) {
        return getIdentifier(node.expression);
      }
      return null;
    }

isArrowIIFE(node: TSESTree.Node): boolean

Parameters:

  • node TSESTree.Node

Returns: boolean

Code
function isArrowIIFE(node: TSESTree.Node): boolean {
      return (
        node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
        node.parent.type === AST_NODE_TYPES.CallExpression
      );
    }

Generated by Syntax Scribe