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 000000000000..17d019cd85a5 --- /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 41afc88199f2..29a47ec2384a 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 000000000000..16038b885342 --- /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 511f9679f111..17064fbedace 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 944a7452a350..d41e711714d6 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 000000000000..199ed893876d --- /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 b3adbd1a3ac7..fd86d1901b1f 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 16038b885342..9ad84e5e0623 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 17d019cd85a5..f7c356698dd1 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 199ed893876d..4d34be93bc84 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 4d34be93bc84..6354cd5d8513 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 f7c356698dd1..1ccdeab215f8 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 9ad84e5e0623..1a0d260dbee4 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 1ccdeab215f8..eaceb6131e04 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 1a0d260dbee4..0d1475e19f40 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 eaceb6131e04..894846ee91ca 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 17064fbedace..247e6cbd2e81 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 d41e711714d6..1997935e3122 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 6354cd5d8513..9923811f3fac 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 894846ee91ca..6e287d1955a7 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 0d1475e19f40..0fe3bcae7fb0 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 0ec5bb30ff46..60a769945183 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 f032ccf4bd81..805f126f8228 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 f649507b1f88..d4521fe7a20d 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 bb51ebfa4f27..a9c91f7c1ca1 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 6e287d1955a7..bdf5707a193f 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 bdf5707a193f..0df1412bd7bb 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 7ecc7ab1b095..17893d25b50a 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 d4521fe7a20d..db717dbcbd3d 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