From b936c86482e7c647d3e5726cab849dcc6638bd1f Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Fri, 12 Apr 2024 02:36:23 +0900 Subject: [PATCH 1/8] feat(eslint-plugin): [no-unnecessary-property-assignment] add new rule --- ...ecessary-parameter-property-assignment.mdx | 42 ++ packages/eslint-plugin/src/configs/all.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 3 + ...necessary-parameter-property-assignment.ts | 241 ++++++++++ ...cessary-parameter-property-assignment.shot | 22 + ...sary-parameter-property-assignment.test.ts | 450 ++++++++++++++++++ ...cessary-parameter-property-assignment.shot | 14 + packages/typescript-eslint/src/configs/all.ts | 2 + 8 files changed, 775 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx create mode 100644 packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts create mode 100644 packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-parameter-property-assignment.shot create mode 100644 packages/eslint-plugin/tests/rules/no-unnecessary-parameter-property-assignment.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-parameter-property-assignment.shot diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx new file mode 100644 index 000000000000..97b787f3cab7 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx @@ -0,0 +1,42 @@ +--- +description: 'Disallow unnecessary assignment of constructor property parameter.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-unnecessary-parameter-property-assignment** for documentation. + +Parameter properties let you create and initialize a member in one place. +Therefore, in most cases, it is not necessary to assign parameter properties of the same name to members within a constructor. + +## Examples + + + + +```ts +class Foo { + constructor(public bar: string) { + this.bar = bar; + } +} +``` + + + + +```ts +class Foo { + constructor(public bar: string) {} +} +``` + + + + +## When Not To Use It + +If you need to assign a parameter property to a member within the constructor, then you don't need to use this rule. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 1e7304e45a36..437dd9e71ed7 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -92,6 +92,7 @@ export = { '@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-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 64c6a872ed69..2c2a3afa94a3 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -80,6 +80,7 @@ import noThrowLiteral from './no-throw-literal'; import noTypeAlias from './no-type-alias'; import noUnnecessaryBooleanLiteralCompare from './no-unnecessary-boolean-literal-compare'; import noUnnecessaryCondition from './no-unnecessary-condition'; +import noUnnecessaryParameterPropertyAssignment from './no-unnecessary-parameter-property-assignment'; import noUnnecessaryQualifier from './no-unnecessary-qualifier'; import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments'; import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; @@ -225,6 +226,8 @@ export default { 'no-type-alias': noTypeAlias, 'no-unnecessary-boolean-literal-compare': noUnnecessaryBooleanLiteralCompare, 'no-unnecessary-condition': noUnnecessaryCondition, + 'no-unnecessary-parameter-property-assignment': + noUnnecessaryParameterPropertyAssignment, 'no-unnecessary-qualifier': noUnnecessaryQualifier, 'no-unnecessary-type-arguments': noUnnecessaryTypeArguments, 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts new file mode 100644 index 000000000000..57204a831992 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts @@ -0,0 +1,241 @@ +import { DefinitionType } from '@typescript-eslint/scope-manager'; +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; + +import { createRule, getStaticStringValue, nullThrows } from '../util'; + +type MessageIds = 'unnecessaryAssign'; + +const UNNECESSARY_OPERATORS = new Set(['=', '&&=', '||=', '??=']); + +export default createRule<[], MessageIds>({ + name: 'no-unnecessary-parameter-property-assignment', + meta: { + docs: { + description: + 'Disallow unnecessary assignment of constructor property parameter', + requiresTypeChecking: false, + }, + fixable: 'code', + messages: { + unnecessaryAssign: + 'This assignment is unnecessary since it already assigned by parameter property.', + }, + schema: [], + type: 'suggestion', + }, + defaultOptions: [], + create(context) { + const reportInfoStack: { + assignedBeforeUnnecessary: Set; + assignedBeforeCtor: Set; + unnecessaryAssigments: { + name: string; + node: TSESTree.AssignmentExpression; + }[]; + }[] = []; + + function getPropertyName(node: TSESTree.MemberExpression): string | null { + if (node.property.type === AST_NODE_TYPES.Identifier) { + return node.property.name; + } + if (node.computed) { + return getStaticStringValue(node.property); + } + return null; + } + + function findFunction( + 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 findFunction(node.parent); + } + + function isThisMemberExpression( + node: TSESTree.Node, + ): node is TSESTree.MemberExpression { + return ( + node.type === AST_NODE_TYPES.MemberExpression && + node.object.type === AST_NODE_TYPES.ThisExpression + ); + } + + function findPropertyDefinition( + node: TSESTree.Node | undefined, + ): TSESTree.PropertyDefinition | undefined { + if (!node || node.type === AST_NODE_TYPES.PropertyDefinition) { + return node; + } + return findPropertyDefinition(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; + } + + return { + ClassBody(): void { + reportInfoStack.push({ + unnecessaryAssigments: [], + assignedBeforeUnnecessary: new Set(), + assignedBeforeCtor: new Set(), + }); + }, + 'ClassBody:exit'(): void { + const { unnecessaryAssigments, assignedBeforeCtor } = nullThrows( + reportInfoStack.pop(), + 'The top stack should exist', + ); + unnecessaryAssigments.forEach(({ name, node }) => { + if (assignedBeforeCtor.has(name)) { + return; + } + context.report({ + node, + messageId: 'unnecessaryAssign', + }); + }); + }, + 'PropertyDefinition AssignmentExpression'( + node: TSESTree.AssignmentExpression, + ): void { + if (!isThisMemberExpression(node.left)) { + return; + } + + const name = getPropertyName(node.left); + + if (!name) { + return; + } + + const functionNode = findFunction(node); + if (functionNode) { + const isArrowIIFE = + functionNode.type === AST_NODE_TYPES.ArrowFunctionExpression && + functionNode.parent.type === AST_NODE_TYPES.CallExpression; + + if ( + !( + isArrowIIFE && + findPropertyDefinition(node)?.value === functionNode.parent + ) + ) { + return; + } + } + + const { assignedBeforeCtor } = nullThrows( + reportInfoStack.at(reportInfoStack.length - 1), + 'The top stack should exist', + ); + assignedBeforeCtor.add(name); + }, + "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"( + node: TSESTree.AssignmentExpression, + ): void { + if (!isThisMemberExpression(node.left)) { + return; + } + + const leftName = getPropertyName(node.left); + + if (!leftName) { + return; + } + + const functionNode = findFunction(node); + + if (!isConstructorFunctionExpression(functionNode)) { + return; + } + + const { assignedBeforeUnnecessary, unnecessaryAssigments } = 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 ( + !rightId || + leftName !== rightId.name || + !isReferenceFromParameter(rightId) + ) { + return; + } + + const hasParameterPropety = functionNode.params.some(param => + isParameterPropertyWithName(param, rightId.name), + ); + + if (hasParameterPropety) { + if (!assignedBeforeUnnecessary.has(leftName)) { + unnecessaryAssigments.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..59d09cfc8baf --- /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 already assigned by 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..dfd44d066041 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-parameter-property-assignment.test.ts @@ -0,0 +1,450 @@ +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', + }, + ], + }, + ], +}); 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 efe0d16fceb8..a500c7bfb062 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-type-arguments': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', From 06cc50ea71f47f3fe20ecf451179ef3807fedb44 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 5 Jun 2024 02:00:27 +0900 Subject: [PATCH 2/8] apply reviews --- ...necessary-parameter-property-assignment.ts | 96 +++++++++---------- ...sary-parameter-property-assignment.test.ts | 36 +++++++ 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts index 57204a831992..46f7ab64449a 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts @@ -28,14 +28,27 @@ export default createRule<[], MessageIds>({ create(context) { const reportInfoStack: { assignedBeforeUnnecessary: Set; - assignedBeforeCtor: Set; + assignedBeforeConstructor: Set; unnecessaryAssigments: { name: string; node: TSESTree.AssignmentExpression; }[]; }[] = []; - function getPropertyName(node: TSESTree.MemberExpression): string | null { + 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; } @@ -45,7 +58,7 @@ export default createRule<[], MessageIds>({ return null; } - function findFunction( + function findParentFunction( node: TSESTree.Node | undefined, ): | TSESTree.FunctionExpression @@ -60,25 +73,16 @@ export default createRule<[], MessageIds>({ ) { return node; } - return findFunction(node.parent); - } - - function isThisMemberExpression( - node: TSESTree.Node, - ): node is TSESTree.MemberExpression { - return ( - node.type === AST_NODE_TYPES.MemberExpression && - node.object.type === AST_NODE_TYPES.ThisExpression - ); + return findParentFunction(node.parent); } - function findPropertyDefinition( + function findParentPropertyDefinition( node: TSESTree.Node | undefined, ): TSESTree.PropertyDefinition | undefined { if (!node || node.type === AST_NODE_TYPES.PropertyDefinition) { return node; } - return findPropertyDefinition(node.parent); + return findParentPropertyDefinition(node.parent); } function isConstructorFunctionExpression( @@ -126,21 +130,28 @@ export default createRule<[], MessageIds>({ 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({ unnecessaryAssigments: [], assignedBeforeUnnecessary: new Set(), - assignedBeforeCtor: new Set(), + assignedBeforeConstructor: new Set(), }); }, 'ClassBody:exit'(): void { - const { unnecessaryAssigments, assignedBeforeCtor } = nullThrows( + const { unnecessaryAssigments, assignedBeforeConstructor } = nullThrows( reportInfoStack.pop(), 'The top stack should exist', ); unnecessaryAssigments.forEach(({ name, node }) => { - if (assignedBeforeCtor.has(name)) { + if (assignedBeforeConstructor.has(name)) { return; } context.report({ @@ -152,52 +163,43 @@ export default createRule<[], MessageIds>({ 'PropertyDefinition AssignmentExpression'( node: TSESTree.AssignmentExpression, ): void { - if (!isThisMemberExpression(node.left)) { - return; - } - const name = getPropertyName(node.left); if (!name) { return; } - const functionNode = findFunction(node); + const functionNode = findParentFunction(node); if (functionNode) { - const isArrowIIFE = - functionNode.type === AST_NODE_TYPES.ArrowFunctionExpression && - functionNode.parent.type === AST_NODE_TYPES.CallExpression; - if ( !( - isArrowIIFE && - findPropertyDefinition(node)?.value === functionNode.parent + isArrowIIFE(functionNode) && + findParentPropertyDefinition(node)?.value === functionNode.parent ) ) { return; } } - const { assignedBeforeCtor } = nullThrows( - reportInfoStack.at(reportInfoStack.length - 1), + const { assignedBeforeConstructor } = nullThrows( + reportInfoStack.at(-1), 'The top stack should exist', ); - assignedBeforeCtor.add(name); + assignedBeforeConstructor.add(name); }, "MethodDefinition[kind='constructor'] > FunctionExpression AssignmentExpression"( node: TSESTree.AssignmentExpression, ): void { - if (!isThisMemberExpression(node.left)) { - return; - } - const leftName = getPropertyName(node.left); if (!leftName) { return; } - const functionNode = findFunction(node); + let functionNode = findParentFunction(node); + if (functionNode && isArrowIIFE(functionNode)) { + functionNode = findParentFunction(functionNode.parent); + } if (!isConstructorFunctionExpression(functionNode)) { return; @@ -215,25 +217,19 @@ export default createRule<[], MessageIds>({ const rightId = getIdentifier(node.right); - if ( - !rightId || - leftName !== rightId.name || - !isReferenceFromParameter(rightId) - ) { + if (leftName !== rightId?.name || !isReferenceFromParameter(rightId)) { return; } - const hasParameterPropety = functionNode.params.some(param => + const hasParameterProperty = functionNode.params.some(param => isParameterPropertyWithName(param, rightId.name), ); - if (hasParameterPropety) { - if (!assignedBeforeUnnecessary.has(leftName)) { - unnecessaryAssigments.push({ - name: leftName, - node, - }); - } + if (hasParameterProperty && !assignedBeforeUnnecessary.has(leftName)) { + unnecessaryAssigments.push({ + name: leftName, + node, + }); } }, }; 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 index dfd44d066041..68001a622e39 100644 --- 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 @@ -446,5 +446,41 @@ class Foo { }, ], }, + { + 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', + }, + ], + }, ], }); From d4867cd9743bb33e75d8c24439a8c4cc181ab0e6 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 5 Jun 2024 02:03:37 +0900 Subject: [PATCH 3/8] remove useless docs --- .../rules/no-unnecessary-parameter-property-assignment.mdx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx index 97b787f3cab7..0965023bc036 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx @@ -36,7 +36,3 @@ class Foo { - -## When Not To Use It - -If you need to assign a parameter property to a member within the constructor, then you don't need to use this rule. From 400cdde7927da0337414f720deba3d085f1184db Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Wed, 5 Jun 2024 02:26:55 +0900 Subject: [PATCH 4/8] add comment --- .../docs/rules/no-unnecessary-parameter-property-assignment.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx index 0965023bc036..16353b471650 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx @@ -36,3 +36,5 @@ class Foo { + +{/* Intentionally Omitted: When Not To Use It */} From 68902d82b7d9bd89c5f9e8e6287143b1168fa53a Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Thu, 13 Jun 2024 21:57:15 +0900 Subject: [PATCH 5/8] fix typo --- ...necessary-parameter-property-assignment.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts index 46f7ab64449a..bec0b3c38726 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts @@ -19,7 +19,7 @@ export default createRule<[], MessageIds>({ fixable: 'code', messages: { unnecessaryAssign: - 'This assignment is unnecessary since it already assigned by parameter property.', + 'This assignment is unnecessary since it is already assigned by a parameter property.', }, schema: [], type: 'suggestion', @@ -29,7 +29,7 @@ export default createRule<[], MessageIds>({ const reportInfoStack: { assignedBeforeUnnecessary: Set; assignedBeforeConstructor: Set; - unnecessaryAssigments: { + unnecessaryAssignments: { name: string; node: TSESTree.AssignmentExpression; }[]; @@ -140,17 +140,15 @@ export default createRule<[], MessageIds>({ return { ClassBody(): void { reportInfoStack.push({ - unnecessaryAssigments: [], + unnecessaryAssignments: [], assignedBeforeUnnecessary: new Set(), assignedBeforeConstructor: new Set(), }); }, 'ClassBody:exit'(): void { - const { unnecessaryAssigments, assignedBeforeConstructor } = nullThrows( - reportInfoStack.pop(), - 'The top stack should exist', - ); - unnecessaryAssigments.forEach(({ name, node }) => { + const { unnecessaryAssignments, assignedBeforeConstructor } = + nullThrows(reportInfoStack.pop(), 'The top stack should exist'); + unnecessaryAssignments.forEach(({ name, node }) => { if (assignedBeforeConstructor.has(name)) { return; } @@ -205,10 +203,11 @@ export default createRule<[], MessageIds>({ return; } - const { assignedBeforeUnnecessary, unnecessaryAssigments } = nullThrows( - reportInfoStack.at(reportInfoStack.length - 1), - 'The top of stack should exist', - ); + const { assignedBeforeUnnecessary, unnecessaryAssignments } = + nullThrows( + reportInfoStack.at(reportInfoStack.length - 1), + 'The top of stack should exist', + ); if (!UNNECESSARY_OPERATORS.has(node.operator)) { assignedBeforeUnnecessary.add(leftName); @@ -226,7 +225,7 @@ export default createRule<[], MessageIds>({ ); if (hasParameterProperty && !assignedBeforeUnnecessary.has(leftName)) { - unnecessaryAssigments.push({ + unnecessaryAssignments.push({ name: leftName, node, }); From 128e3b571283278f9f6c6669d20854e6ddd6bcf8 Mon Sep 17 00:00:00 2001 From: yeonjuan Date: Thu, 13 Jun 2024 23:06:00 +0900 Subject: [PATCH 6/8] update snapshots --- .../no-unnecessary-parameter-property-assignment.shot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 59d09cfc8baf..c3108fe12164 100644 --- 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 @@ -6,7 +6,7 @@ exports[`Validating rule docs no-unnecessary-parameter-property-assignment.mdx c class Foo { constructor(public bar: string) { this.bar = bar; - ~~~~~~~~~~~~~~ This assignment is unnecessary since it already assigned by parameter property. + ~~~~~~~~~~~~~~ This assignment is unnecessary since it is already assigned by a parameter property. } } " From d22cb8b1a65c4ed9563045b49a44d46762e91900 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 30 Jun 2024 19:57:19 +0900 Subject: [PATCH 7/8] apply review --- .../rules/no-unnecessary-parameter-property-assignment.mdx | 6 ++++-- .../rules/no-unnecessary-parameter-property-assignment.ts | 5 +---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx index 16353b471650..836ac8bd67b4 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-parameter-property-assignment.mdx @@ -9,7 +9,7 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-unnecessary-parameter-property-assignment** for documentation. -Parameter properties let you create and initialize a member in one place. +[TypeScript's parameter properties](https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties) allow creating and initializing a member in one place. Therefore, in most cases, it is not necessary to assign parameter properties of the same name to members within a constructor. ## Examples @@ -37,4 +37,6 @@ class Foo { -{/* Intentionally Omitted: When Not To Use It */} +## When Not To Use It + +If you don't use parameter properties, you can ignore this rule. diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts index bec0b3c38726..43cc5ebbcf24 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-parameter-property-assignment.ts @@ -4,17 +4,14 @@ import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils'; import { createRule, getStaticStringValue, nullThrows } from '../util'; -type MessageIds = 'unnecessaryAssign'; - const UNNECESSARY_OPERATORS = new Set(['=', '&&=', '||=', '??=']); -export default createRule<[], MessageIds>({ +export default createRule({ name: 'no-unnecessary-parameter-property-assignment', meta: { docs: { description: 'Disallow unnecessary assignment of constructor property parameter', - requiresTypeChecking: false, }, fixable: 'code', messages: { From b2690e48222276dfb34ddea3bcf0f440b8c4ca73 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 30 Jun 2024 20:17:09 +0900 Subject: [PATCH 8/8] trigger action 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