From 8e5d39f4047d10f399b7fa77f494fc19784db704 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 7 May 2022 17:44:21 +0800 Subject: [PATCH 01/14] feat(eslint-plugin): new rule consistent-generic-constructors --- .../rules/consistent-generic-constructors.ts | 87 ++++++++++++ packages/eslint-plugin/src/rules/index.ts | 2 + .../consistent-generic-constructors.test.ts | 132 ++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 packages/eslint-plugin/src/rules/consistent-generic-constructors.ts create mode 100644 packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts new file mode 100644 index 00000000000..17d019cd85a --- /dev/null +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -0,0 +1,87 @@ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import { createRule } from '../util'; + +type MessageIds = 'preferLHS' | 'preferRHS'; +type Options = ['lhs' | 'rhs']; + +export default createRule({ + name: 'consistent-generic-constructors', + meta: { + type: 'suggestion', + docs: { + description: + 'Enforce specifying generic type arguments on LHS or RHS of constructor call', + recommended: false, + }, + messages: { + preferLHS: + 'The generic type arguments should be specified on the left-hand side of the constructor call.', + preferRHS: + 'The generic type arguments should be specified on the right-hand side of the constructor call.', + }, + fixable: 'code', + schema: [ + { + enum: ['lhs', 'rhs'], + }, + ], + }, + defaultOptions: ['rhs'], + create(context, [mode]) { + return { + VariableDeclarator(node: TSESTree.VariableDeclarator): void { + const sourceCode = context.getSourceCode(); + const lhs = node.id.typeAnnotation?.typeAnnotation; + const rhs = node.init; + if ( + !rhs || + rhs.type !== AST_NODE_TYPES.NewExpression || + rhs.callee.type !== AST_NODE_TYPES.Identifier + ) { + return; + } + if ( + lhs && + (lhs.type !== AST_NODE_TYPES.TSTypeReference || + lhs.typeName.type !== AST_NODE_TYPES.Identifier) + ) { + return; + } + if (mode === 'lhs' && !lhs && rhs.typeParameters) { + context.report({ + node, + messageId: 'preferLHS', + fix(fixer) { + const { typeParameters, callee } = rhs; + const typeAnnotation = + sourceCode.getText(callee) + sourceCode.getText(typeParameters); + return [ + fixer.remove(typeParameters!), + fixer.insertTextAfter(node.id, ': ' + typeAnnotation), + ]; + }, + }); + } else if ( + mode === 'rhs' && + lhs?.typeParameters && + !rhs.typeParameters && + (lhs.typeName as TSESTree.Identifier).name === rhs.callee.name + ) { + context.report({ + node, + messageId: 'preferRHS', + fix(fixer) { + return [ + fixer.remove(lhs.parent!), + fixer.insertTextAfter( + rhs.callee, + sourceCode.getText(lhs.typeParameters), + ), + ]; + }, + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 41afc88199f..29a47ec2384 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -8,6 +8,7 @@ import braceStyle from './brace-style'; import classLiteralPropertyStyle from './class-literal-property-style'; import commaDangle from './comma-dangle'; import commaSpacing from './comma-spacing'; +import consistentGenericConstructors from './consistent-generic-constructors'; import consistentIndexedObjectStyle from './consistent-indexed-object-style'; import consistentTypeAssertions from './consistent-type-assertions'; import consistentTypeDefinitions from './consistent-type-definitions'; @@ -136,6 +137,7 @@ export default { 'class-literal-property-style': classLiteralPropertyStyle, 'comma-dangle': commaDangle, 'comma-spacing': commaSpacing, + 'consistent-generic-constructors': consistentGenericConstructors, 'consistent-indexed-object-style': consistentIndexedObjectStyle, 'consistent-type-assertions': consistentTypeAssertions, 'consistent-type-definitions': consistentTypeDefinitions, diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts new file mode 100644 index 00000000000..16038b88534 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -0,0 +1,132 @@ +import rule from '../../src/rules/consistent-generic-constructors'; +import { RuleTester, noFormat } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('consistent-generic-constructors', rule, { + valid: [ + // default: rhs + 'const a = new Foo();', + 'const a = new Foo();', + 'const a: Foo = new Foo();', + 'const a: Foo = new Foo();', + 'const a: Bar = new Foo();', + 'const a: Foo = new Foo();', + 'const a: Bar = new Foo();', + 'const a: Bar = new Foo();', + // lhs + { + code: 'const a = new Foo();', + options: ['lhs'], + }, + { + code: 'const a: Foo = new Foo();', + options: ['lhs'], + }, + { + code: 'const a: Foo = new Foo();', + options: ['lhs'], + }, + { + code: 'const a: Foo = new Foo();', + options: ['lhs'], + }, + { + code: 'const a: Bar = new Foo();', + options: ['lhs'], + }, + { + code: 'const a: Bar = new Foo();', + options: ['lhs'], + }, + ], + invalid: [ + { + code: 'const a: Foo = new Foo();', + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: 'const a = new Foo();', + }, + { + code: 'const a: Map = new Map();', + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: 'const a = new Map();', + }, + { + code: noFormat`const a: Map = new Map();`, + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: noFormat`const a = new Map();`, + }, + { + code: noFormat`const a: Map< string, number > = new Map();`, + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: noFormat`const a = new Map< string, number >();`, + }, + { + code: noFormat`const a: Map = new Map ();`, + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: noFormat`const a = new Map ();`, + }, + { + code: 'const a = new Foo();', + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: 'const a: Foo = new Foo();', + }, + { + code: 'const a = new Map();', + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: 'const a: Map = new Map();', + }, + { + code: noFormat`const a = new Map ();`, + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: noFormat`const a: Map = new Map ();`, + }, + { + code: noFormat`const a = new Map< string, number >();`, + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: noFormat`const a: Map< string, number > = new Map();`, + }, + ], +}); From df8de5ca3db9578c88e4010b358d733d7fb8338b Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 7 May 2022 18:23:30 +0800 Subject: [PATCH 02/14] docs: basic docs --- packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/docs/rules/README.md | 1 + .../rules/consistent-generic-constructors.md | 85 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/consistent-generic-constructors.md diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 511f9679f11..17064fbedac 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -104,6 +104,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/ban-tslint-comment`](./docs/rules/ban-tslint-comment.md) | Bans `// tslint:` comments from being used | | :wrench: | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./docs/rules/class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./docs/rules/consistent-generic-constructors.md) | Enforce specifying generic type arguments on LHS or RHS of constructor call | | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./docs/rules/consistent-indexed-object-style.md) | Enforce or disallow the use of the record type | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | | | | | [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/README.md b/packages/eslint-plugin/docs/rules/README.md index 944a7452a35..d41e711714d 100644 --- a/packages/eslint-plugin/docs/rules/README.md +++ b/packages/eslint-plugin/docs/rules/README.md @@ -28,6 +28,7 @@ slug: / | [`@typescript-eslint/ban-tslint-comment`](./ban-tslint-comment.md) | Bans `// tslint:` comments from being used | | :wrench: | | | [`@typescript-eslint/ban-types`](./ban-types.md) | Bans specific types from being used | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./consistent-generic-constructors.md) | Enforce specifying generic type arguments on LHS or RHS of constructor call | | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./consistent-indexed-object-style.md) | Enforce or disallow the use of the record type | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./consistent-type-assertions.md) | Enforces consistent usage of type assertions | | | | | [`@typescript-eslint/consistent-type-definitions`](./consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md new file mode 100644 index 00000000000..199ed893876 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -0,0 +1,85 @@ +# `consistent-generic-constructors` + +Enforce specifying generic type arguments on LHS or RHS of constructor call. + +When constructing a generic class, you can specify the type arguments on either the left-hand side or the right-hand side: + +```ts +// Left-hand side +const map: Map = new Map(); + +// Right-hand side +const map = new Map(); +``` + +This rule ensures that type arguments appear consistently on one side of the declaration. + +## Options + +```jsonc +{ + "rules": { + "@typescript-eslint/consistent-generic-constructors": ["error", "rhs"] + } +} +``` + +This rule takes a string option: + +- If it's set to `rhs` (default), only type arguments on the right-hand side are allowed. +- If it's set to `lhs`, only type arguments on the left-hand side are allowed. + +## Rule Details + +The rule never reports when there are type parameters on both sides, or neither sides of the declaration. It also doesn't report if the names of the two sides don't match. + +### `rhs` + + + +#### ❌ Incorrect + +```ts +const map: Map = new Map(); +const set: Set = new Set(); +``` + +#### ✅ Correct + +```ts +const map = new Map(); +const map: Map = new MyMap(); +const set = new Set(); +const set = new Set(); +const set: Set = new Set(); +``` + +### `lhs` + + + +#### ❌ Incorrect + +```ts +const map = new Map(); +const set = new Set(); +``` + +#### ✅ Correct + +```ts +const map: Map = new Map(); +const set: Set = new Set(); +const set = new Set(); +const set: Set = new Set(); +``` + +## When Not To Use It + +You can turn this rule off if you don't want to enforce one kind of generic constructor style over the other. + +## Attributes + +- [ ] ✅ Recommended +- [x] 🔧 Fixable +- [ ] 💭 Requires type information From aa7c312d76eedbef71644a9a170e413505624ef6 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 7 May 2022 19:47:28 +0800 Subject: [PATCH 03/14] fix: add to all config --- packages/eslint-plugin/src/configs/all.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index b3adbd1a3ac..fd86d1901b1 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -18,6 +18,7 @@ export = { '@typescript-eslint/comma-dangle': 'error', 'comma-spacing': 'off', '@typescript-eslint/comma-spacing': 'error', + '@typescript-eslint/consistent-generic-constructors': 'error', '@typescript-eslint/consistent-indexed-object-style': 'error', '@typescript-eslint/consistent-type-assertions': 'error', '@typescript-eslint/consistent-type-definitions': 'error', From c6434c7a32c11e35a377422016ceb88f2a64fa02 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 7 May 2022 20:28:42 +0800 Subject: [PATCH 04/14] test: add test --- .../consistent-generic-constructors.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts index 16038b88534..9ad84e5e062 100644 --- a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -16,6 +16,9 @@ ruleTester.run('consistent-generic-constructors', rule, { 'const a: Foo = new Foo();', 'const a: Bar = new Foo();', 'const a: Bar = new Foo();', + 'const a: Foo = Foo();', + 'const a: Foo = Foo();', + 'const a: Foo = Foo();', // lhs { code: 'const a = new Foo();', @@ -41,6 +44,22 @@ ruleTester.run('consistent-generic-constructors', rule, { code: 'const a: Bar = new Foo();', options: ['lhs'], }, + { + code: 'const a: Foo = Foo();', + options: ['lhs'], + }, + { + code: 'const a: Foo = Foo();', + options: ['lhs'], + }, + { + code: 'const a: Foo = Foo();', + options: ['lhs'], + }, + { + code: 'const a = new (class C {})();', + options: ['lhs'], + }, ], invalid: [ { From 7e29ee3de3471daedadad58eb5b96311472fef4f Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 7 May 2022 22:39:25 +0800 Subject: [PATCH 05/14] refactor: refactor --- .../src/rules/consistent-generic-constructors.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 17d019cd85a..f7c356698dd 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -43,7 +43,8 @@ export default createRule({ if ( lhs && (lhs.type !== AST_NODE_TYPES.TSTypeReference || - lhs.typeName.type !== AST_NODE_TYPES.Identifier) + lhs.typeName.type !== AST_NODE_TYPES.Identifier || + lhs.typeName.name !== rhs.callee.name) ) { return; } @@ -61,12 +62,8 @@ export default createRule({ ]; }, }); - } else if ( - mode === 'rhs' && - lhs?.typeParameters && - !rhs.typeParameters && - (lhs.typeName as TSESTree.Identifier).name === rhs.callee.name - ) { + } + if (mode === 'rhs' && lhs?.typeParameters && !rhs.typeParameters) { context.report({ node, messageId: 'preferRHS', From 85b851c97bb788a9333e901b0593c2e77044fa70 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sun, 8 May 2022 00:50:09 +0800 Subject: [PATCH 06/14] docs: fix --- .../eslint-plugin/docs/rules/consistent-generic-constructors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md index 199ed893876..4d34be93bc8 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -41,7 +41,7 @@ The rule never reports when there are type parameters on both sides, or neither ```ts const map: Map = new Map(); -const set: Set = new Set(); +const set: Set = new Set(); ``` #### ✅ Correct From 33cb2d3ba14ab31f0e6af59028a42e9adecf422f Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Thu, 12 May 2022 23:23:17 +0800 Subject: [PATCH 07/14] Update consistent-generic-constructors.md --- .../docs/rules/consistent-generic-constructors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md index 4d34be93bc8..6354cd5d851 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -26,8 +26,8 @@ This rule ensures that type arguments appear consistently on one side of the dec This rule takes a string option: -- If it's set to `rhs` (default), only type arguments on the right-hand side are allowed. -- If it's set to `lhs`, only type arguments on the left-hand side are allowed. +- If it's set to `rhs` (default), type arguments that **only** appear on the left-hand side are disallowed. +- If it's set to `lhs`, type arguments that **only** appear on the right-hand side are disallowed. ## Rule Details From ba54b9eb4fafe4b4c2c5ba07354ee1fab877f9ad Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 14 May 2022 11:30:33 +0800 Subject: [PATCH 08/14] fix: allow no parens on RHS --- .../rules/consistent-generic-constructors.ts | 26 +++++---- .../consistent-generic-constructors.test.ts | 58 +++++++++++++++++++ 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index f7c356698dd..1ccdeab215f 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -49,32 +49,34 @@ export default createRule({ return; } if (mode === 'lhs' && !lhs && rhs.typeParameters) { + const { typeParameters, callee } = rhs; + const typeAnnotation = + sourceCode.getText(callee) + sourceCode.getText(typeParameters); context.report({ node, messageId: 'preferLHS', fix(fixer) { - const { typeParameters, callee } = rhs; - const typeAnnotation = - sourceCode.getText(callee) + sourceCode.getText(typeParameters); return [ - fixer.remove(typeParameters!), + fixer.remove(typeParameters), fixer.insertTextAfter(node.id, ': ' + typeAnnotation), ]; }, }); } if (mode === 'rhs' && lhs?.typeParameters && !rhs.typeParameters) { + const hasParens = sourceCode.getTokenAfter(rhs.callee)?.value === '('; context.report({ node, messageId: 'preferRHS', - fix(fixer) { - return [ - fixer.remove(lhs.parent!), - fixer.insertTextAfter( - rhs.callee, - sourceCode.getText(lhs.typeParameters), - ), - ]; + *fix(fixer) { + yield fixer.remove(lhs.parent!); + yield fixer.insertTextAfter( + rhs.callee, + sourceCode.getText(lhs.typeParameters), + ); + if (!hasParens) { + yield fixer.insertTextAfter(rhs.callee, '()'); + } }, }); } diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts index 9ad84e5e062..1a0d260dbee 100644 --- a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -107,6 +107,44 @@ ruleTester.run('consistent-generic-constructors', rule, { ], output: noFormat`const a = new Map ();`, }, + { + code: noFormat`const a: Foo = new Foo;`, + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: noFormat`const a = new Foo();`, + }, + { + code: 'const a: Foo/* comment */ = new Foo();', + errors: [ + { + messageId: 'preferRHS', + }, + ], + // FIXME + output: 'const a = new Foo();', + }, + { + code: 'const a: Foo/* comment */ = new Foo /* another */();', + errors: [ + { + messageId: 'preferRHS', + }, + ], + // FIXME + output: 'const a = new Foo /* another */();', + }, + { + code: noFormat`const a: Foo = new \n Foo \n ();`, + errors: [ + { + messageId: 'preferRHS', + }, + ], + output: noFormat`const a = new \n Foo \n ();`, + }, { code: 'const a = new Foo();', options: ['lhs'], @@ -147,5 +185,25 @@ ruleTester.run('consistent-generic-constructors', rule, { ], output: noFormat`const a: Map< string, number > = new Map();`, }, + { + code: noFormat`const a = new \n Foo \n ();`, + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: noFormat`const a: Foo = new \n Foo \n ();`, + }, + { + code: 'const a = new Foo/* comment */ /* another */();', + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: noFormat`const a: Foo = new Foo/* comment */ /* another */();`, + }, ], }); From 46de1018080c00eacc5555c0d19b07064863d745 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 14 May 2022 12:14:29 +0800 Subject: [PATCH 09/14] fix: print extra comments --- .../rules/consistent-generic-constructors.ts | 13 +++++++++++++ .../consistent-generic-constructors.test.ts | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 1ccdeab215f..eaceb6131e0 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -65,11 +65,24 @@ export default createRule({ } if (mode === 'rhs' && lhs?.typeParameters && !rhs.typeParameters) { const hasParens = sourceCode.getTokenAfter(rhs.callee)?.value === '('; + const extraComments = new Set( + sourceCode.getCommentsInside(lhs.parent!), + ); + sourceCode + .getCommentsInside(lhs.typeParameters) + .forEach(c => extraComments.delete(c)); context.report({ node, messageId: 'preferRHS', *fix(fixer) { yield fixer.remove(lhs.parent!); + for (const comment of extraComments) { + yield fixer.insertTextAfter( + rhs.callee, + // @ts-expect-error: `sourceCode.getText` should accept `TSESTree.Comment` + sourceCode.getText(comment), + ); + } yield fixer.insertTextAfter( rhs.callee, sourceCode.getText(lhs.typeParameters), diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts index 1a0d260dbee..0d1475e19f4 100644 --- a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -117,14 +117,13 @@ ruleTester.run('consistent-generic-constructors', rule, { output: noFormat`const a = new Foo();`, }, { - code: 'const a: Foo/* comment */ = new Foo();', + code: 'const a: /* comment */ Foo/* another */ = new Foo();', errors: [ { messageId: 'preferRHS', }, ], - // FIXME - output: 'const a = new Foo();', + output: noFormat`const a = new Foo/* comment *//* another */();`, }, { code: 'const a: Foo/* comment */ = new Foo /* another */();', @@ -133,8 +132,7 @@ ruleTester.run('consistent-generic-constructors', rule, { messageId: 'preferRHS', }, ], - // FIXME - output: 'const a = new Foo /* another */();', + output: noFormat`const a = new Foo/* comment */ /* another */();`, }, { code: noFormat`const a: Foo = new \n Foo \n ();`, @@ -205,5 +203,15 @@ ruleTester.run('consistent-generic-constructors', rule, { ], output: noFormat`const a: Foo = new Foo/* comment */ /* another */();`, }, + { + code: 'const a = new Foo();', + options: ['lhs'], + errors: [ + { + messageId: 'preferLHS', + }, + ], + output: noFormat`const a: Foo = new Foo();`, + }, ], }); From 867152a65eaf6c64b31631b2a6d1e45866717420 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sat, 14 May 2022 12:21:36 +0800 Subject: [PATCH 10/14] refactor: improve message? --- .../src/rules/consistent-generic-constructors.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index eaceb6131e0..894846ee91c 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; type MessageIds = 'preferLHS' | 'preferRHS'; @@ -15,9 +15,9 @@ export default createRule({ }, messages: { preferLHS: - 'The generic type arguments should be specified on the left-hand side of the constructor call.', + 'The generic type arguments should be specified on the left-hand side of the declaration as a type annotation.', preferRHS: - 'The generic type arguments should be specified on the right-hand side of the constructor call.', + 'The generic type arguments should be specified on the right-hand side of the declaration as constructor type arguments.', }, fixable: 'code', schema: [ @@ -29,7 +29,7 @@ export default createRule({ defaultOptions: ['rhs'], create(context, [mode]) { return { - VariableDeclarator(node: TSESTree.VariableDeclarator): void { + VariableDeclarator(node): void { const sourceCode = context.getSourceCode(); const lhs = node.id.typeAnnotation?.typeAnnotation; const rhs = node.init; From c40ab67e188b5aea7c3d24d11faf7630346af76f Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Sun, 15 May 2022 13:56:12 +0800 Subject: [PATCH 11/14] refactor: lhs/rhs -> type-annotation/constructor --- packages/eslint-plugin/README.md | 2 +- packages/eslint-plugin/docs/rules/README.md | 2 +- .../rules/consistent-generic-constructors.md | 19 ++--- .../rules/consistent-generic-constructors.ts | 30 ++++---- .../consistent-generic-constructors.test.ts | 70 +++++++++---------- 5 files changed, 65 insertions(+), 58 deletions(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 17064fbedac..247e6cbd2e8 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -104,7 +104,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/ban-tslint-comment`](./docs/rules/ban-tslint-comment.md) | Bans `// tslint:` comments from being used | | :wrench: | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Bans specific types from being used | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./docs/rules/class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | -| [`@typescript-eslint/consistent-generic-constructors`](./docs/rules/consistent-generic-constructors.md) | Enforce specifying generic type arguments on LHS or RHS of constructor call | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./docs/rules/consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./docs/rules/consistent-indexed-object-style.md) | Enforce or disallow the use of the record type | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforces consistent usage of type assertions | | | | | [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/README.md b/packages/eslint-plugin/docs/rules/README.md index d41e711714d..1997935e312 100644 --- a/packages/eslint-plugin/docs/rules/README.md +++ b/packages/eslint-plugin/docs/rules/README.md @@ -28,7 +28,7 @@ slug: / | [`@typescript-eslint/ban-tslint-comment`](./ban-tslint-comment.md) | Bans `// tslint:` comments from being used | | :wrench: | | | [`@typescript-eslint/ban-types`](./ban-types.md) | Bans specific types from being used | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./class-literal-property-style.md) | Ensures that literals on classes are exposed in a consistent style | | :wrench: | | -| [`@typescript-eslint/consistent-generic-constructors`](./consistent-generic-constructors.md) | Enforce specifying generic type arguments on LHS or RHS of constructor call | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./consistent-indexed-object-style.md) | Enforce or disallow the use of the record type | | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./consistent-type-assertions.md) | Enforces consistent usage of type assertions | | | | | [`@typescript-eslint/consistent-type-definitions`](./consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md index 6354cd5d851..9923811f3fa 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -1,8 +1,8 @@ # `consistent-generic-constructors` -Enforce specifying generic type arguments on LHS or RHS of constructor call. +Enforce specifying generic type arguments on type annotation or constructor name of a constructor call. -When constructing a generic class, you can specify the type arguments on either the left-hand side or the right-hand side: +When constructing a generic class, you can specify the type arguments on either the left-hand side (as a type annotation) or the right-hand side (as part of the constructor call): ```ts // Left-hand side @@ -19,21 +19,24 @@ This rule ensures that type arguments appear consistently on one side of the dec ```jsonc { "rules": { - "@typescript-eslint/consistent-generic-constructors": ["error", "rhs"] + "@typescript-eslint/consistent-generic-constructors": [ + "error", + "constructor" + ] } } ``` This rule takes a string option: -- If it's set to `rhs` (default), type arguments that **only** appear on the left-hand side are disallowed. -- If it's set to `lhs`, type arguments that **only** appear on the right-hand side are disallowed. +- If it's set to `constructor` (default), type arguments that **only** appear on the type annotation are disallowed. +- If it's set to `type-annotation`, type arguments that **only** appear on the constructor are disallowed. ## Rule Details -The rule never reports when there are type parameters on both sides, or neither sides of the declaration. It also doesn't report if the names of the two sides don't match. +The rule never reports when there are type parameters on both sides, or neither sides of the declaration. It also doesn't report if the names of the type annotation and the constructor don't match. -### `rhs` +### `constructor` @@ -54,7 +57,7 @@ const set = new Set(); const set: Set = new Set(); ``` -### `lhs` +### `type-annotation` diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 894846ee91c..6e287d1955a 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -1,8 +1,8 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule } from '../util'; -type MessageIds = 'preferLHS' | 'preferRHS'; -type Options = ['lhs' | 'rhs']; +type MessageIds = 'preferTypeAnnotation' | 'preferConstructor'; +type Options = ['type-annotation' | 'constructor']; export default createRule({ name: 'consistent-generic-constructors', @@ -10,23 +10,23 @@ export default createRule({ type: 'suggestion', docs: { description: - 'Enforce specifying generic type arguments on LHS or RHS of constructor call', + 'Enforce specifying generic type arguments on type annotation or constructor name of a constructor call', recommended: false, }, messages: { - preferLHS: - 'The generic type arguments should be specified on the left-hand side of the declaration as a type annotation.', - preferRHS: - 'The generic type arguments should be specified on the right-hand side of the declaration as constructor type arguments.', + preferTypeAnnotation: + 'The generic type arguments should be specified as part of the type annotation.', + preferConstructor: + 'The generic type arguments should be specified as part of the constructor type arguments.', }, fixable: 'code', schema: [ { - enum: ['lhs', 'rhs'], + enum: ['type-annotation', 'constructor'], }, ], }, - defaultOptions: ['rhs'], + defaultOptions: ['constructor'], create(context, [mode]) { return { VariableDeclarator(node): void { @@ -48,13 +48,13 @@ export default createRule({ ) { return; } - if (mode === 'lhs' && !lhs && rhs.typeParameters) { + if (mode === 'type-annotation' && !lhs && rhs.typeParameters) { const { typeParameters, callee } = rhs; const typeAnnotation = sourceCode.getText(callee) + sourceCode.getText(typeParameters); context.report({ node, - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', fix(fixer) { return [ fixer.remove(typeParameters), @@ -63,7 +63,11 @@ export default createRule({ }, }); } - if (mode === 'rhs' && lhs?.typeParameters && !rhs.typeParameters) { + if ( + mode === 'constructor' && + lhs?.typeParameters && + !rhs.typeParameters + ) { const hasParens = sourceCode.getTokenAfter(rhs.callee)?.value === '('; const extraComments = new Set( sourceCode.getCommentsInside(lhs.parent!), @@ -73,7 +77,7 @@ export default createRule({ .forEach(c => extraComments.delete(c)); context.report({ node, - messageId: 'preferRHS', + messageId: 'preferConstructor', *fix(fixer) { yield fixer.remove(lhs.parent!); for (const comment of extraComments) { diff --git a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts index 0d1475e19f4..0fe3bcae7fb 100644 --- a/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-generic-constructors.test.ts @@ -7,7 +7,7 @@ const ruleTester = new RuleTester({ ruleTester.run('consistent-generic-constructors', rule, { valid: [ - // default: rhs + // default: constructor 'const a = new Foo();', 'const a = new Foo();', 'const a: Foo = new Foo();', @@ -19,46 +19,46 @@ ruleTester.run('consistent-generic-constructors', rule, { 'const a: Foo = Foo();', 'const a: Foo = Foo();', 'const a: Foo = Foo();', - // lhs + // type-annotation { code: 'const a = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Bar = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Bar = new Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a: Foo = Foo();', - options: ['lhs'], + options: ['type-annotation'], }, { code: 'const a = new (class C {})();', - options: ['lhs'], + options: ['type-annotation'], }, ], invalid: [ @@ -66,7 +66,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: 'const a: Foo = new Foo();', errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: 'const a = new Foo();', @@ -75,7 +75,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: 'const a: Map = new Map();', errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: 'const a = new Map();', @@ -84,7 +84,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: noFormat`const a: Map = new Map();`, errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Map();`, @@ -93,7 +93,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: noFormat`const a: Map< string, number > = new Map();`, errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Map< string, number >();`, @@ -102,7 +102,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: noFormat`const a: Map = new Map ();`, errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Map ();`, @@ -111,7 +111,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: noFormat`const a: Foo = new Foo;`, errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Foo();`, @@ -120,7 +120,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: 'const a: /* comment */ Foo/* another */ = new Foo();', errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Foo/* comment *//* another */();`, @@ -129,7 +129,7 @@ ruleTester.run('consistent-generic-constructors', rule, { code: 'const a: Foo/* comment */ = new Foo /* another */();', errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new Foo/* comment */ /* another */();`, @@ -138,77 +138,77 @@ ruleTester.run('consistent-generic-constructors', rule, { code: noFormat`const a: Foo = new \n Foo \n ();`, errors: [ { - messageId: 'preferRHS', + messageId: 'preferConstructor', }, ], output: noFormat`const a = new \n Foo \n ();`, }, { code: 'const a = new Foo();', - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: 'const a: Foo = new Foo();', }, { code: 'const a = new Map();', - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: 'const a: Map = new Map();', }, { code: noFormat`const a = new Map ();`, - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: noFormat`const a: Map = new Map ();`, }, { code: noFormat`const a = new Map< string, number >();`, - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: noFormat`const a: Map< string, number > = new Map();`, }, { code: noFormat`const a = new \n Foo \n ();`, - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: noFormat`const a: Foo = new \n Foo \n ();`, }, { code: 'const a = new Foo/* comment */ /* another */();', - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: noFormat`const a: Foo = new Foo/* comment */ /* another */();`, }, { code: 'const a = new Foo();', - options: ['lhs'], + options: ['type-annotation'], errors: [ { - messageId: 'preferLHS', + messageId: 'preferTypeAnnotation', }, ], output: noFormat`const a: Foo = new Foo();`, From 371983e45974a753086577ad6b442a1c71d1612a Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Mon, 30 May 2022 16:11:40 +0800 Subject: [PATCH 12/14] feat: add to strict --- packages/eslint-plugin/README.md | 2 +- packages/eslint-plugin/docs/rules/README.md | 2 +- .../docs/rules/consistent-generic-constructors.md | 4 +++- packages/eslint-plugin/src/configs/strict.ts | 1 + .../src/rules/consistent-generic-constructors.ts | 2 +- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 0ec5bb30ff4..60a76994518 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -102,7 +102,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/ban-tslint-comment`](./docs/rules/ban-tslint-comment.md) | Disallow `// tslint:` comments | :lock: | :wrench: | | | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Disallow certain types | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./docs/rules/class-literal-property-style.md) | Enforce that literals on classes are exposed in a consistent style | :lock: | :wrench: | | -| [`@typescript-eslint/consistent-generic-constructors`](./docs/rules/consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./docs/rules/consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | :lock: | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./docs/rules/consistent-indexed-object-style.md) | Require or disallow the `Record` type | :lock: | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./docs/rules/consistent-type-assertions.md) | Enforce consistent usage of type assertions | :lock: | | | | [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Enforce type definitions to consistently use either `interface` or `type` | :lock: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/README.md b/packages/eslint-plugin/docs/rules/README.md index f032ccf4bd8..805f126f822 100644 --- a/packages/eslint-plugin/docs/rules/README.md +++ b/packages/eslint-plugin/docs/rules/README.md @@ -24,7 +24,7 @@ See [Configs](/docs/linting/configs) for how to enable recommended rules using c | [`@typescript-eslint/ban-tslint-comment`](./ban-tslint-comment.md) | Disallow `// tslint:` comments | :lock: | :wrench: | | | [`@typescript-eslint/ban-types`](./ban-types.md) | Disallow certain types | :white_check_mark: | :wrench: | | | [`@typescript-eslint/class-literal-property-style`](./class-literal-property-style.md) | Enforce that literals on classes are exposed in a consistent style | :lock: | :wrench: | | -| [`@typescript-eslint/consistent-generic-constructors`](./consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | | :wrench: | | +| [`@typescript-eslint/consistent-generic-constructors`](./consistent-generic-constructors.md) | Enforce specifying generic type arguments on type annotation or constructor name of a constructor call | :lock: | :wrench: | | | [`@typescript-eslint/consistent-indexed-object-style`](./consistent-indexed-object-style.md) | Require or disallow the `Record` type | :lock: | :wrench: | | | [`@typescript-eslint/consistent-type-assertions`](./consistent-type-assertions.md) | Enforce consistent usage of type assertions | :lock: | | | | [`@typescript-eslint/consistent-type-definitions`](./consistent-type-definitions.md) | Enforce type definitions to consistently use either `interface` or `type` | :lock: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md index f649507b1f8..d4521fe7a20 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -83,6 +83,8 @@ You can turn this rule off if you don't want to enforce one kind of generic cons ## Attributes -- [ ] ✅ Recommended +- Configs: + - [ ] ✅ Recommended + - [x] 🔒 Strict - [x] 🔧 Fixable - [ ] 💭 Requires type information diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index bb51ebfa4f2..a9c91f7c1ca 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -9,6 +9,7 @@ export = { '@typescript-eslint/ban-tslint-comment': 'warn', '@typescript-eslint/class-literal-property-style': 'warn', '@typescript-eslint/consistent-indexed-object-style': 'warn', + '@typescript-eslint/consistent-generic-constructors': 'warn', '@typescript-eslint/consistent-type-assertions': 'warn', '@typescript-eslint/consistent-type-definitions': 'warn', 'dot-notation': 'off', diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index 6e287d1955a..bdf5707a193 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -11,7 +11,7 @@ export default createRule({ docs: { description: 'Enforce specifying generic type arguments on type annotation or constructor name of a constructor call', - recommended: false, + recommended: 'strict', }, messages: { preferTypeAnnotation: From be2f1afd32b8e926946c5c6bffa5cc9597a12016 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 31 May 2022 19:25:51 +0800 Subject: [PATCH 13/14] refactors --- .../rules/consistent-generic-constructors.ts | 91 ++++++++++--------- packages/utils/src/ts-eslint/SourceCode.ts | 2 +- 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts index bdf5707a193..0df1412bd7b 100644 --- a/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts +++ b/packages/eslint-plugin/src/rules/consistent-generic-constructors.ts @@ -28,9 +28,9 @@ export default createRule({ }, defaultOptions: ['constructor'], create(context, [mode]) { + const sourceCode = context.getSourceCode(); return { VariableDeclarator(node): void { - const sourceCode = context.getSourceCode(); const lhs = node.id.typeAnnotation?.typeAnnotation; const rhs = node.init; if ( @@ -48,54 +48,55 @@ export default createRule({ ) { return; } - if (mode === 'type-annotation' && !lhs && rhs.typeParameters) { - const { typeParameters, callee } = rhs; - const typeAnnotation = - sourceCode.getText(callee) + sourceCode.getText(typeParameters); - context.report({ - node, - messageId: 'preferTypeAnnotation', - fix(fixer) { - return [ - fixer.remove(typeParameters), - fixer.insertTextAfter(node.id, ': ' + typeAnnotation), - ]; - }, - }); + if (mode === 'type-annotation') { + if (!lhs && rhs.typeParameters) { + const { typeParameters, callee } = rhs; + const typeAnnotation = + sourceCode.getText(callee) + sourceCode.getText(typeParameters); + context.report({ + node, + messageId: 'preferTypeAnnotation', + fix(fixer) { + return [ + fixer.remove(typeParameters), + fixer.insertTextAfter(node.id, ': ' + typeAnnotation), + ]; + }, + }); + } + return; } - if ( - mode === 'constructor' && - lhs?.typeParameters && - !rhs.typeParameters - ) { - const hasParens = sourceCode.getTokenAfter(rhs.callee)?.value === '('; - const extraComments = new Set( - sourceCode.getCommentsInside(lhs.parent!), - ); - sourceCode - .getCommentsInside(lhs.typeParameters) - .forEach(c => extraComments.delete(c)); - context.report({ - node, - messageId: 'preferConstructor', - *fix(fixer) { - yield fixer.remove(lhs.parent!); - for (const comment of extraComments) { + if (mode === 'constructor') { + if (lhs?.typeParameters && !rhs.typeParameters) { + const hasParens = + sourceCode.getTokenAfter(rhs.callee)?.value === '('; + const extraComments = new Set( + sourceCode.getCommentsInside(lhs.parent!), + ); + sourceCode + .getCommentsInside(lhs.typeParameters) + .forEach(c => extraComments.delete(c)); + context.report({ + node, + messageId: 'preferConstructor', + *fix(fixer) { + yield fixer.remove(lhs.parent!); + for (const comment of extraComments) { + yield fixer.insertTextAfter( + rhs.callee, + sourceCode.getText(comment), + ); + } yield fixer.insertTextAfter( rhs.callee, - // @ts-expect-error: `sourceCode.getText` should accept `TSESTree.Comment` - sourceCode.getText(comment), + sourceCode.getText(lhs.typeParameters), ); - } - yield fixer.insertTextAfter( - rhs.callee, - sourceCode.getText(lhs.typeParameters), - ); - if (!hasParens) { - yield fixer.insertTextAfter(rhs.callee, '()'); - } - }, - }); + if (!hasParens) { + yield fixer.insertTextAfter(rhs.callee, '()'); + } + }, + }); + } } }, }; diff --git a/packages/utils/src/ts-eslint/SourceCode.ts b/packages/utils/src/ts-eslint/SourceCode.ts index 7ecc7ab1b09..17893d25b50 100644 --- a/packages/utils/src/ts-eslint/SourceCode.ts +++ b/packages/utils/src/ts-eslint/SourceCode.ts @@ -276,7 +276,7 @@ declare class SourceCodeBase extends TokenStore { * @returns The text representing the AST node. */ getText( - node?: TSESTree.Node, + node?: TSESTree.Node | TSESTree.Token, beforeCount?: number, afterCount?: number, ): string; From 1bdedec002ce5d90745476380efad6d3a4047ef8 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Tue, 31 May 2022 19:28:40 +0800 Subject: [PATCH 14/14] remove attr from source --- .../docs/rules/consistent-generic-constructors.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md index d4521fe7a20..db717dbcbd3 100644 --- a/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md +++ b/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md @@ -80,11 +80,3 @@ const set: Set = new Set(); ## When Not To Use It You can turn this rule off if you don't want to enforce one kind of generic constructor style over the other. - -## Attributes - -- Configs: - - [ ] ✅ Recommended - - [x] 🔒 Strict -- [x] 🔧 Fixable -- [ ] 💭 Requires type information