From feb9556936ab3623f9baec631bf498529e775fe1 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 23 Oct 2023 11:28:28 +0200 Subject: [PATCH 01/13] feat(eslint-plugin): [no-spread-function] Add new rule `no-spread-function`. --- .../docs/rules/no-spread-function.md | 45 +++++++ .../src/rules/no-spread-function.ts | 42 +++++++ .../tests/rules/no-spread-function.test.ts | 110 ++++++++++++++++++ 3 files changed, 197 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-spread-function.md create mode 100644 packages/eslint-plugin/src/rules/no-spread-function.ts create mode 100644 packages/eslint-plugin/tests/rules/no-spread-function.test.ts diff --git a/packages/eslint-plugin/docs/rules/no-spread-function.md b/packages/eslint-plugin/docs/rules/no-spread-function.md new file mode 100644 index 000000000000..dc66e311522f --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-spread-function.md @@ -0,0 +1,45 @@ +--- +description: 'Disallow spread operator on function.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-spread-function** for documentation. + +Spreading a function is almost always a mistake. Most of the time you forgot to call the function. + +## Examples + + + +### ❌ Incorrect + +```ts +const fn = () => ({ name: 'name' }); +const obj = { + ...fn, + value: 1, +}; +``` + +```ts +const fn = () => ({ value: 33 }); +const otherFn = ({ value: number }) => ({ value: value }); +otherFn({ ...fn }); +``` + +### ✅ Correct + +```ts +const fn = () => ({ name: 'name' }); +const obj = { + ...fn(), + value: 1, +}; +``` + +```ts +const fn = () => ({ value: 33 }); +const otherFn = ({ value: number }) => ({ value: value }); +otherFn({ ...fn() }); +``` diff --git a/packages/eslint-plugin/src/rules/no-spread-function.ts b/packages/eslint-plugin/src/rules/no-spread-function.ts new file mode 100644 index 000000000000..b3a54b1c0673 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-spread-function.ts @@ -0,0 +1,42 @@ +import { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { createRule, isFunction } from '../util'; + +export default createRule({ + defaultOptions: [], + name: 'no-spread-function', + meta: { + docs: { + description: 'Disallow spread operator on function.', + recommended: 'stylistic', + requiresTypeChecking: true, + }, + messages: { + forbidden: + 'Spreading a function is almost always a mistake. Did you forgot to call the function?', + }, + schema: [], + type: 'suggestion', + }, + create: (context): TSESLint.RuleListener => { + const listener = (node: TSESTree.SpreadElement): void => { + const svc = ESLintUtils.getParserServices(context); + const tc = svc.program.getTypeChecker(); + + const tsNode = svc.esTreeNodeToTSNodeMap.get(node.argument); + const type = tc.getTypeAtLocation(tsNode); + + if ( + type.getProperties().length === 0 && + type.getCallSignatures().length > 0 + ) { + context.report({ + node, + messageId: 'forbidden', + }); + } + }; + return { + SpreadElement: listener, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-spread-function.test.ts b/packages/eslint-plugin/tests/rules/no-spread-function.test.ts new file mode 100644 index 000000000000..5329069e6beb --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-spread-function.test.ts @@ -0,0 +1,110 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-spread-function'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, +}); + +ruleTester.run('no-spread-function', rule, { + valid: [ + ` +const a = () => ({ value: 33 }); +const b = { + ...a(), + name: 'name' +}; +`, + ` +const a = (x: number) => ({ value: x }); +const b = { + ...a(42), + name: 'name' +}; +`, + ` +interface FuncWithProps { + property?: string; + (): number; +} +const funcWithProps: FuncWithProps = () => 1; +funcWithProps.property = 'foo'; +const spreadFuncWithProps = {...funcWithProps}; +`, + ` +type FuncWithProps = { + property?: string; + (): number; +} +const funcWithProps: FuncWithProps = () => 1; +funcWithProps.property = 'foo'; +const spreadFuncWithProps = {...funcWithProps}; +`, + ` +const a = { value: 33 }; +const b = { + ...a, + name: 'name' +}; +`, + ` +const a = {}; +const b = { + ...a, + name: 'name' +}; +`, + ` +const a = () => ({ value: 33 }); +const b = ({value: number}) => ({value: value}) +b({...a()}) +`, + ` +const a = () => ({ value: 33 }); +const b = ({value: number}) => ({value: value}) +`, + ` +const a = [33]; +const b = { + ...a, + name: 'name' +}; +`, + ], + invalid: [ + { + code: ` +const a = () => ({ value: 33 }); +const b = { + ...a, + name: 'name' +}; + `, + errors: [{ line: 4, column: 3, messageId: 'forbidden' }], + }, + { + code: ` +const a = (x: number) => ({ value: x }); +const b = { + ...a, + name: 'name' +}; + `, + errors: [{ line: 4, column: 3, messageId: 'forbidden' }], + }, + { + code: ` +const a = () => ({ value: 33 }); +const b = ({value: number}) => ({value: value}) +b({...a}) + `, + errors: [{ line: 4, column: 4, messageId: 'forbidden' }], + }, + ], +}); From cc72332077dd029253d8190883ab962a4cde4db2 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 23 Oct 2023 12:45:50 +0200 Subject: [PATCH 02/13] Fix lint & test issues --- packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-spread-function.ts | 11 ++-- .../tests/rules/no-spread-function.test.ts | 50 +++++++++---------- .../schema-snapshots/no-spread-function.shot | 14 ++++++ 4 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 9d87b8cac412..bf0fbaa1a32a 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -71,6 +71,7 @@ import noRedundantTypeConstituents from './no-redundant-type-constituents'; import noRequireImports from './no-require-imports'; import noRestrictedImports from './no-restricted-imports'; import noShadow from './no-shadow'; +import noSpreadFunction from './no-spread-function'; import noThisAlias from './no-this-alias'; import noThrowLiteral from './no-throw-literal'; import noTypeAlias from './no-type-alias'; @@ -208,6 +209,7 @@ export default { 'no-require-imports': noRequireImports, 'no-restricted-imports': noRestrictedImports, 'no-shadow': noShadow, + 'no-spread-function': noSpreadFunction, 'no-this-alias': noThisAlias, 'no-throw-literal': noThrowLiteral, 'no-type-alias': noTypeAlias, diff --git a/packages/eslint-plugin/src/rules/no-spread-function.ts b/packages/eslint-plugin/src/rules/no-spread-function.ts index b3a54b1c0673..9d3dfb77c35c 100644 --- a/packages/eslint-plugin/src/rules/no-spread-function.ts +++ b/packages/eslint-plugin/src/rules/no-spread-function.ts @@ -1,12 +1,17 @@ -import { ESLintUtils, TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { createRule, isFunction } from '../util'; +import { + ESLintUtils, + type TSESLint, + type TSESTree, +} from '@typescript-eslint/utils'; + +import { createRule } from '../util'; export default createRule({ defaultOptions: [], name: 'no-spread-function', meta: { docs: { - description: 'Disallow spread operator on function.', + description: 'Disallow spread operator on function', recommended: 'stylistic', requiresTypeChecking: true, }, diff --git a/packages/eslint-plugin/tests/rules/no-spread-function.test.ts b/packages/eslint-plugin/tests/rules/no-spread-function.test.ts index 5329069e6beb..9296ced766ca 100644 --- a/packages/eslint-plugin/tests/rules/no-spread-function.test.ts +++ b/packages/eslint-plugin/tests/rules/no-spread-function.test.ts @@ -18,16 +18,16 @@ ruleTester.run('no-spread-function', rule, { const a = () => ({ value: 33 }); const b = { ...a(), - name: 'name' + name: 'name', }; -`, + `, ` const a = (x: number) => ({ value: x }); const b = { ...a(42), - name: 'name' + name: 'name', }; -`, + `, ` interface FuncWithProps { property?: string; @@ -35,47 +35,47 @@ interface FuncWithProps { } const funcWithProps: FuncWithProps = () => 1; funcWithProps.property = 'foo'; -const spreadFuncWithProps = {...funcWithProps}; -`, +const spreadFuncWithProps = { ...funcWithProps }; + `, ` type FuncWithProps = { property?: string; (): number; -} +}; const funcWithProps: FuncWithProps = () => 1; funcWithProps.property = 'foo'; -const spreadFuncWithProps = {...funcWithProps}; -`, +const spreadFuncWithProps = { ...funcWithProps }; + `, ` const a = { value: 33 }; const b = { ...a, - name: 'name' + name: 'name', }; -`, + `, ` const a = {}; const b = { ...a, - name: 'name' + name: 'name', }; -`, + `, ` const a = () => ({ value: 33 }); -const b = ({value: number}) => ({value: value}) -b({...a()}) -`, +const b = ({ value: number }) => ({ value: value }); +b({ ...a() }); + `, ` const a = () => ({ value: 33 }); -const b = ({value: number}) => ({value: value}) -`, +const b = ({ value: number }) => ({ value: value }); + `, ` const a = [33]; const b = { ...a, - name: 'name' + name: 'name', }; -`, + `, ], invalid: [ { @@ -83,7 +83,7 @@ const b = { const a = () => ({ value: 33 }); const b = { ...a, - name: 'name' + name: 'name', }; `, errors: [{ line: 4, column: 3, messageId: 'forbidden' }], @@ -93,7 +93,7 @@ const b = { const a = (x: number) => ({ value: x }); const b = { ...a, - name: 'name' + name: 'name', }; `, errors: [{ line: 4, column: 3, messageId: 'forbidden' }], @@ -101,10 +101,10 @@ const b = { { code: ` const a = () => ({ value: 33 }); -const b = ({value: number}) => ({value: value}) -b({...a}) +const b = ({ value: number }) => ({ value: value }); +b({ ...a }); `, - errors: [{ line: 4, column: 4, messageId: 'forbidden' }], + errors: [{ line: 4, column: 5, messageId: 'forbidden' }], }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot b/packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot new file mode 100644 index 000000000000..7cbfaf7de51d --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-spread-function 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; From bcf64c3439ae17d7135405f524c7284e357d2652 Mon Sep 17 00:00:00 2001 From: Laurent Date: Mon, 23 Oct 2023 12:56:45 +0200 Subject: [PATCH 03/13] Fix config tests --- packages/eslint-plugin/src/configs/all.ts | 1 + packages/eslint-plugin/src/configs/stylistic-type-checked.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index f7a3cc3dbcb0..1dc6c410f0d0 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -108,6 +108,7 @@ export = { 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', 'no-shadow': 'off', + '@typescript-eslint/no-spread-function': 'error', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-this-alias': 'error', 'no-throw-literal': 'off', diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts index 5c73ae3845b6..5da1455ce6e1 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts @@ -22,6 +22,7 @@ export = { 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-spread-function': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', '@typescript-eslint/prefer-for-of': 'error', From f0b5507b961b23db498b789fcfa8624ae3d99c5b Mon Sep 17 00:00:00 2001 From: Laurent Date: Tue, 24 Oct 2023 09:03:38 +0200 Subject: [PATCH 04/13] Rename rule from `no-spread-function` to `no-misused-spread` --- .../rules/{no-spread-function.md => no-misused-spread.md} | 2 +- packages/eslint-plugin/src/configs/all.ts | 2 +- packages/eslint-plugin/src/configs/stylistic-type-checked.ts | 2 +- packages/eslint-plugin/src/rules/index.ts | 4 ++-- .../src/rules/{no-spread-function.ts => no-misused-spread.ts} | 2 +- .../{no-spread-function.test.ts => no-misused-spread.test.ts} | 4 ++-- .../{no-spread-function.shot => no-misused-spread.shot} | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename packages/eslint-plugin/docs/rules/{no-spread-function.md => no-misused-spread.md} (89%) rename packages/eslint-plugin/src/rules/{no-spread-function.ts => no-misused-spread.ts} (97%) rename packages/eslint-plugin/tests/rules/{no-spread-function.test.ts => no-misused-spread.test.ts} (95%) rename packages/eslint-plugin/tests/schema-snapshots/{no-spread-function.shot => no-misused-spread.shot} (80%) diff --git a/packages/eslint-plugin/docs/rules/no-spread-function.md b/packages/eslint-plugin/docs/rules/no-misused-spread.md similarity index 89% rename from packages/eslint-plugin/docs/rules/no-spread-function.md rename to packages/eslint-plugin/docs/rules/no-misused-spread.md index dc66e311522f..7d864f77ace5 100644 --- a/packages/eslint-plugin/docs/rules/no-spread-function.md +++ b/packages/eslint-plugin/docs/rules/no-misused-spread.md @@ -4,7 +4,7 @@ description: 'Disallow spread operator on function.' > 🛑 This file is source code, not the primary documentation location! 🛑 > -> See **https://typescript-eslint.io/rules/no-spread-function** for documentation. +> See **https://typescript-eslint.io/rules/no-misused-spread** for documentation. Spreading a function is almost always a mistake. Most of the time you forgot to call the function. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 1dc6c410f0d0..92c30f46f046 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -97,6 +97,7 @@ export = { '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-mixed-enums': 'error', + '@typescript-eslint/no-misused-spread': 'error', '@typescript-eslint/no-namespace': 'error', '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', @@ -108,7 +109,6 @@ export = { 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', 'no-shadow': 'off', - '@typescript-eslint/no-spread-function': 'error', '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-this-alias': 'error', 'no-throw-literal': 'off', diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts index 5da1455ce6e1..7844e5a1513a 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts @@ -22,8 +22,8 @@ export = { 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-spread-function': 'error', '@typescript-eslint/no-inferrable-types': 'error', + '@typescript-eslint/no-misused-spread': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index bf0fbaa1a32a..ff88c2a6b91e 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -61,6 +61,7 @@ import noMagicNumbers from './no-magic-numbers'; import noMeaninglessVoidOperator from './no-meaningless-void-operator'; import noMisusedNew from './no-misused-new'; import noMisusedPromises from './no-misused-promises'; +import noMisusedSpread from './no-misused-spread'; import noMixedEnums from './no-mixed-enums'; import noNamespace from './no-namespace'; import noNonNullAssertedNullishCoalescing from './no-non-null-asserted-nullish-coalescing'; @@ -71,7 +72,6 @@ import noRedundantTypeConstituents from './no-redundant-type-constituents'; import noRequireImports from './no-require-imports'; import noRestrictedImports from './no-restricted-imports'; import noShadow from './no-shadow'; -import noSpreadFunction from './no-spread-function'; import noThisAlias from './no-this-alias'; import noThrowLiteral from './no-throw-literal'; import noTypeAlias from './no-type-alias'; @@ -199,6 +199,7 @@ export default { 'no-meaningless-void-operator': noMeaninglessVoidOperator, 'no-misused-new': noMisusedNew, 'no-misused-promises': noMisusedPromises, + 'no-misused-spread': noMisusedSpread, 'no-mixed-enums': noMixedEnums, 'no-namespace': noNamespace, 'no-non-null-asserted-nullish-coalescing': noNonNullAssertedNullishCoalescing, @@ -209,7 +210,6 @@ export default { 'no-require-imports': noRequireImports, 'no-restricted-imports': noRestrictedImports, 'no-shadow': noShadow, - 'no-spread-function': noSpreadFunction, 'no-this-alias': noThisAlias, 'no-throw-literal': noThrowLiteral, 'no-type-alias': noTypeAlias, diff --git a/packages/eslint-plugin/src/rules/no-spread-function.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts similarity index 97% rename from packages/eslint-plugin/src/rules/no-spread-function.ts rename to packages/eslint-plugin/src/rules/no-misused-spread.ts index 9d3dfb77c35c..9eb4f290e358 100644 --- a/packages/eslint-plugin/src/rules/no-spread-function.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -8,7 +8,7 @@ import { createRule } from '../util'; export default createRule({ defaultOptions: [], - name: 'no-spread-function', + name: 'no-misused-spread', meta: { docs: { description: 'Disallow spread operator on function', diff --git a/packages/eslint-plugin/tests/rules/no-spread-function.test.ts b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts similarity index 95% rename from packages/eslint-plugin/tests/rules/no-spread-function.test.ts rename to packages/eslint-plugin/tests/rules/no-misused-spread.test.ts index 9296ced766ca..b222205366ab 100644 --- a/packages/eslint-plugin/tests/rules/no-spread-function.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -1,6 +1,6 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; -import rule from '../../src/rules/no-spread-function'; +import rule from '../../src/rules/no-misused-spread'; import { getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); @@ -12,7 +12,7 @@ const ruleTester = new RuleTester({ }, }); -ruleTester.run('no-spread-function', rule, { +ruleTester.run('no-misused-spread', rule, { valid: [ ` const a = () => ({ value: 33 }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot b/packages/eslint-plugin/tests/schema-snapshots/no-misused-spread.shot similarity index 80% rename from packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot rename to packages/eslint-plugin/tests/schema-snapshots/no-misused-spread.shot index 7cbfaf7de51d..2f6b196189c8 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-spread-function.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-misused-spread.shot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Rule schemas should be convertible to TS types for documentation purposes no-spread-function 1`] = ` +exports[`Rule schemas should be convertible to TS types for documentation purposes no-misused-spread 1`] = ` " # SCHEMA: From 28eaaaa53bcb46f3061f35c545ab18805a127a6d Mon Sep 17 00:00:00 2001 From: lcharlois-neotys Date: Wed, 22 Nov 2023 06:11:19 +0100 Subject: [PATCH 05/13] Update packages/eslint-plugin/src/rules/no-misused-spread.ts Co-authored-by: StyleShit <32631382+StyleShit@users.noreply.github.com> --- packages/eslint-plugin/src/rules/no-misused-spread.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 9eb4f290e358..7daa1991fa7e 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -17,7 +17,7 @@ export default createRule({ }, messages: { forbidden: - 'Spreading a function is almost always a mistake. Did you forgot to call the function?', + 'Spreading a function is almost always a mistake. Did you forget to call the function?', }, schema: [], type: 'suggestion', From 4377ed4540c53d7bfa3e3cec5c2b97b8c6b56a69 Mon Sep 17 00:00:00 2001 From: lcharlois-neotys Date: Wed, 22 Nov 2023 06:14:57 +0100 Subject: [PATCH 06/13] Update packages/eslint-plugin/src/rules/no-misused-spread.ts Co-authored-by: StyleShit <32631382+StyleShit@users.noreply.github.com> --- packages/eslint-plugin/src/rules/no-misused-spread.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 7daa1991fa7e..66eb162eb060 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -24,7 +24,7 @@ export default createRule({ }, create: (context): TSESLint.RuleListener => { const listener = (node: TSESTree.SpreadElement): void => { - const svc = ESLintUtils.getParserServices(context); + const services = getParserServices(context); const tc = svc.program.getTypeChecker(); const tsNode = svc.esTreeNodeToTSNodeMap.get(node.argument); From a8c6469b95fd9bdeda0719ff1a031cd0ea55c72c Mon Sep 17 00:00:00 2001 From: lcharlois-neotys Date: Wed, 22 Nov 2023 06:15:04 +0100 Subject: [PATCH 07/13] Update packages/eslint-plugin/src/rules/no-misused-spread.ts Co-authored-by: StyleShit <32631382+StyleShit@users.noreply.github.com> --- packages/eslint-plugin/src/rules/no-misused-spread.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 66eb162eb060..b31d1833c93d 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -25,7 +25,7 @@ export default createRule({ create: (context): TSESLint.RuleListener => { const listener = (node: TSESTree.SpreadElement): void => { const services = getParserServices(context); - const tc = svc.program.getTypeChecker(); + const checker = services.program.getTypeChecker(); const tsNode = svc.esTreeNodeToTSNodeMap.get(node.argument); const type = tc.getTypeAtLocation(tsNode); From 1f23aa77bd65ee56a915c73df3b0516e19f84564 Mon Sep 17 00:00:00 2001 From: lcharlois-neotys Date: Wed, 22 Nov 2023 06:25:13 +0100 Subject: [PATCH 08/13] Update packages/eslint-plugin/src/rules/no-misused-spread.ts Co-authored-by: StyleShit <32631382+StyleShit@users.noreply.github.com> --- packages/eslint-plugin/src/rules/no-misused-spread.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index b31d1833c93d..c8d4cb78e12f 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -6,7 +6,9 @@ import { import { createRule } from '../util'; -export default createRule({ +type MessageIds = 'forbidden'; + +export default createRule<[], MessageIds>({ defaultOptions: [], name: 'no-misused-spread', meta: { From b994177935be150c74ba29657478527590869d57 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Nov 2023 06:26:22 +0100 Subject: [PATCH 09/13] Remove useless test case. --- packages/eslint-plugin/tests/rules/no-misused-spread.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts index b222205366ab..24ad760e8860 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -66,10 +66,6 @@ const b = ({ value: number }) => ({ value: value }); b({ ...a() }); `, ` -const a = () => ({ value: 33 }); -const b = ({ value: number }) => ({ value: value }); - `, - ` const a = [33]; const b = { ...a, From 33ef9f35b4df09e95d37de85f257e0b88ffe6bd7 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Nov 2023 06:34:35 +0100 Subject: [PATCH 10/13] Fix import and code after accepting suggestion --- .../eslint-plugin/src/rules/no-misused-spread.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index c8d4cb78e12f..b62a4f16088e 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,10 +1,6 @@ -import { - ESLintUtils, - type TSESLint, - type TSESTree, -} from '@typescript-eslint/utils'; +import { type TSESLint, type TSESTree } from '@typescript-eslint/utils'; -import { createRule } from '../util'; +import { createRule, getParserServices } from '../util'; type MessageIds = 'forbidden'; @@ -29,8 +25,8 @@ export default createRule<[], MessageIds>({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); - const tsNode = svc.esTreeNodeToTSNodeMap.get(node.argument); - const type = tc.getTypeAtLocation(tsNode); + const tsNode = services.esTreeNodeToTSNodeMap.get(node.argument); + const type = checker.getTypeAtLocation(tsNode); if ( type.getProperties().length === 0 && From 34a9dbce0fa77cea5663a5a9d884f55043efc2f6 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Nov 2023 06:37:02 +0100 Subject: [PATCH 11/13] Make rule description more generic. --- packages/eslint-plugin/docs/rules/no-misused-spread.md | 2 +- packages/eslint-plugin/src/rules/no-misused-spread.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-misused-spread.md b/packages/eslint-plugin/docs/rules/no-misused-spread.md index 7d864f77ace5..14828f7ac09d 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-spread.md +++ b/packages/eslint-plugin/docs/rules/no-misused-spread.md @@ -1,5 +1,5 @@ --- -description: 'Disallow spread operator on function.' +description: 'Disallow spread operator that shouldn\'t be spread most of the time.' --- > 🛑 This file is source code, not the primary documentation location! 🛑 diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index b62a4f16088e..dee28db43ab0 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -9,7 +9,8 @@ export default createRule<[], MessageIds>({ name: 'no-misused-spread', meta: { docs: { - description: 'Disallow spread operator on function', + description: + "Disallow spread operator that shouldn't be spread most of the time", recommended: 'stylistic', requiresTypeChecking: true, }, From d84cf04f77ee5f3b0ef1b200fead0b7372b561a5 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Nov 2023 06:39:57 +0100 Subject: [PATCH 12/13] Change message id. --- packages/eslint-plugin/src/rules/no-misused-spread.ts | 6 +++--- .../eslint-plugin/tests/rules/no-misused-spread.test.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index dee28db43ab0..544a70c6428a 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -2,7 +2,7 @@ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils'; import { createRule, getParserServices } from '../util'; -type MessageIds = 'forbidden'; +type MessageIds = 'forbiddenFunctionSpread'; export default createRule<[], MessageIds>({ defaultOptions: [], @@ -15,7 +15,7 @@ export default createRule<[], MessageIds>({ requiresTypeChecking: true, }, messages: { - forbidden: + forbiddenFunctionSpread: 'Spreading a function is almost always a mistake. Did you forget to call the function?', }, schema: [], @@ -35,7 +35,7 @@ export default createRule<[], MessageIds>({ ) { context.report({ node, - messageId: 'forbidden', + messageId: 'forbiddenFunctionSpread', }); } }; diff --git a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts index 24ad760e8860..2d70b1f2623b 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -82,7 +82,7 @@ const b = { name: 'name', }; `, - errors: [{ line: 4, column: 3, messageId: 'forbidden' }], + errors: [{ line: 4, column: 3, messageId: 'forbiddenFunctionSpread' }], }, { code: ` @@ -92,7 +92,7 @@ const b = { name: 'name', }; `, - errors: [{ line: 4, column: 3, messageId: 'forbidden' }], + errors: [{ line: 4, column: 3, messageId: 'forbiddenFunctionSpread' }], }, { code: ` @@ -100,7 +100,7 @@ const a = () => ({ value: 33 }); const b = ({ value: number }) => ({ value: value }); b({ ...a }); `, - errors: [{ line: 4, column: 5, messageId: 'forbidden' }], + errors: [{ line: 4, column: 5, messageId: 'forbiddenFunctionSpread' }], }, ], }); From 4aed59badc8cb4fada50b2adcf2a728547bf9300 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 22 Nov 2023 07:23:57 +0100 Subject: [PATCH 13/13] Fixing quotes in documentation. --- packages/eslint-plugin/docs/rules/no-misused-spread.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-misused-spread.md b/packages/eslint-plugin/docs/rules/no-misused-spread.md index 14828f7ac09d..605f1eb1cd44 100644 --- a/packages/eslint-plugin/docs/rules/no-misused-spread.md +++ b/packages/eslint-plugin/docs/rules/no-misused-spread.md @@ -1,5 +1,5 @@ --- -description: 'Disallow spread operator that shouldn\'t be spread most of the time.' +description: "Disallow spread operator that shouldn't be spread most of the time." --- > 🛑 This file is source code, not the primary documentation location! 🛑