;
+ 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.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.FunctionExpression
+ | TSESTree.FunctionDeclaration
+ | TSESTree.ArrowFunctionExpression
+ | 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.type === AST_NODE_TYPES.Identifier &&
+ 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({
+ unnecessaryAssignments: [],
+ assignedBeforeUnnecessary: new Set(),
+ assignedBeforeConstructor: new Set(),
+ });
+ },
+ 'ClassBody:exit'(): void {
+ const { unnecessaryAssignments, assignedBeforeConstructor } =
+ nullThrows(reportInfoStack.pop(), 'The top stack should exist');
+ unnecessaryAssignments.forEach(({ name, node }) => {
+ if (assignedBeforeConstructor.has(name)) {
+ return;
+ }
+ context.report({
+ node,
+ messageId: 'unnecessaryAssign',
+ });
+ });
+ },
+ 'PropertyDefinition AssignmentExpression'(
+ node: TSESTree.AssignmentExpression,
+ ): void {
+ const name = getPropertyName(node.left);
+
+ if (!name) {
+ return;
+ }
+
+ const functionNode = findParentFunction(node);
+ if (functionNode) {
+ if (
+ !(
+ isArrowIIFE(functionNode) &&
+ findParentPropertyDefinition(node)?.value === functionNode.parent
+ )
+ ) {
+ return;
+ }
+ }
+
+ const { assignedBeforeConstructor } = nullThrows(
+ reportInfoStack.at(-1),
+ 'The top stack should exist',
+ );
+ assignedBeforeConstructor.add(name);
+ },
+ "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,
+ });
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-parameter-property-assignment.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-parameter-property-assignment.shot
new file mode 100644
index 000000000000..c3108fe12164
--- /dev/null
+++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-parameter-property-assignment.shot
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Validating rule docs no-unnecessary-parameter-property-assignment.mdx code examples ESLint output 1`] = `
+"Incorrect
+
+class Foo {
+ constructor(public bar: string) {
+ this.bar = bar;
+ ~~~~~~~~~~~~~~ This assignment is unnecessary since it is already assigned by a parameter property.
+ }
+}
+"
+`;
+
+exports[`Validating rule docs no-unnecessary-parameter-property-assignment.mdx code examples ESLint output 2`] = `
+"Correct
+
+class Foo {
+ constructor(public bar: string) {}
+}
+"
+`;
diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-parameter-property-assignment.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-parameter-property-assignment.test.ts
new file mode 100644
index 000000000000..68001a622e39
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-unnecessary-parameter-property-assignment.test.ts
@@ -0,0 +1,486 @@
+import { RuleTester } from '@typescript-eslint/rule-tester';
+
+import rule from '../../src/rules/no-unnecessary-parameter-property-assignment';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ sourceType: 'module',
+ },
+});
+
+ruleTester.run('no-unnecessary-parameter-property-assignment', rule, {
+ valid: [
+ `
+class Foo {
+ constructor(foo: string) {}
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: string) {}
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: string) {
+ this.foo = bar;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: any) {
+ this.foo = foo.bar;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: string) {
+ this.foo = this.bar;
+ }
+}
+ `,
+ `
+class Foo {
+ foo: string;
+ constructor(foo: string) {
+ this.foo = foo;
+ }
+}
+ `,
+ `
+class Foo {
+ bar: string;
+ constructor(private foo: string) {
+ this.bar = foo;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: string) {
+ this.bar = () => {
+ this.foo = foo;
+ };
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(private foo: string) {
+ this[\`\${foo}\`] = foo;
+ }
+}
+ `,
+ `
+function Foo(foo) {
+ this.foo = foo;
+}
+ `,
+ `
+const foo = 'foo';
+this.foo = foo;
+ `,
+ `
+class Foo {
+ constructor(public foo: number) {
+ this.foo += foo;
+ this.foo -= foo;
+ this.foo *= foo;
+ this.foo /= foo;
+ this.foo %= foo;
+ this.foo **= foo;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(public foo: number) {
+ this.foo += 1;
+ this.foo = foo;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(
+ public foo: number,
+ bar: boolean,
+ ) {
+ if (bar) {
+ this.foo += 1;
+ } else {
+ this.foo = foo;
+ }
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(public foo: number) {
+ this.foo = foo;
+ }
+ init = (this.foo += 1);
+}
+ `,
+ `
+class Foo {
+ constructor(public foo: number) {
+ {
+ const foo = 1;
+ this.foo = foo;
+ }
+ }
+}
+ `,
+ `
+declare const name: string;
+class Foo {
+ constructor(public foo: number) {
+ this[name] = foo;
+ }
+}
+ `,
+ `
+declare const name: string;
+class Foo {
+ constructor(public foo: number) {
+ Foo.foo = foo;
+ }
+}
+ `,
+ `
+class Foo {
+ constructor(public foo: number) {
+ this.foo = foo;
+ }
+ init = (() => {
+ this.foo += 1;
+ })();
+}
+ `,
+ `
+declare const name: string;
+class Foo {
+ constructor(public foo: number) {
+ this[name] = foo;
+ }
+ init = (this[name] = 1);
+ init2 = (Foo.foo = 1);
+}
+ `,
+ ],
+ invalid: [
+ {
+ code: `
+class Foo {
+ constructor(public foo: string) {
+ this.foo = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo?: string) {
+ this.foo = foo!;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo?: string) {
+ this.foo = foo as any;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo = '') {
+ this.foo = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo = '') {
+ this.foo = foo;
+ this.foo += 'foo';
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo: string) {
+ this.foo ||= foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo: string) {
+ this.foo ??= foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(public foo: string) {
+ this.foo &&= foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ this['foo'] = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ function bar() {
+ this.foo = foo;
+ }
+ this.foo = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 7,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ this.bar = () => {
+ this.foo = foo;
+ };
+ this.foo = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 7,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ class Bar {
+ constructor(private foo: string) {
+ this.foo = foo;
+ }
+ }
+ this.foo = foo;
+ }
+}
+ `,
+ errors: [
+ {
+ line: 6,
+ column: 9,
+ messageId: 'unnecessaryAssign',
+ },
+ {
+ line: 9,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ this.foo = foo;
+ }
+ bar = () => {
+ this.foo = 'foo';
+ };
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ this.foo = foo;
+ }
+ init = foo => {
+ this.foo = foo;
+ };
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ this.foo = foo;
+ }
+ init = class Bar {
+ constructor(private foo: string) {
+ this.foo = foo;
+ }
+ };
+}
+ `,
+ errors: [
+ {
+ line: 4,
+ column: 5,
+ messageId: 'unnecessaryAssign',
+ },
+ {
+ line: 8,
+ column: 7,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ {
+ this.foo = foo;
+ }
+ }
+}
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 7,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ {
+ code: `
+class Foo {
+ constructor(private foo: string) {
+ (() => {
+ this.foo = foo;
+ })();
+ }
+}
+ `,
+ errors: [
+ {
+ line: 5,
+ column: 7,
+ messageId: 'unnecessaryAssign',
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-parameter-property-assignment.shot b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-parameter-property-assignment.shot
new file mode 100644
index 000000000000..eea1075666f7
--- /dev/null
+++ b/packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-parameter-property-assignment.shot
@@ -0,0 +1,14 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Rule schemas should be convertible to TS types for documentation purposes no-unnecessary-parameter-property-assignment 1`] = `
+"
+# SCHEMA:
+
+[]
+
+
+# TYPES:
+
+/** No options declared */
+type Options = [];"
+`;
diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts
index c92e80a1f275..1414a72e14ef 100644
--- a/packages/typescript-eslint/src/configs/all.ts
+++ b/packages/typescript-eslint/src/configs/all.ts
@@ -101,6 +101,8 @@ export default (
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
'@typescript-eslint/no-unnecessary-condition': 'error',
+ '@typescript-eslint/no-unnecessary-parameter-property-assignment':
+ 'error',
'@typescript-eslint/no-unnecessary-qualifier': 'error',
'@typescript-eslint/no-unnecessary-template-expression': 'error',
'@typescript-eslint/no-unnecessary-type-arguments': 'error',
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy