From 4d4ff19fd970ab053262946703cc115a71682755 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 27 Jan 2025 02:19:25 +0900 Subject: [PATCH 1/9] temp --- .../src/rules/no-misused-spread.ts | 68 ++++++- .../tests/rules/no-misused-spread.test.ts | 169 ++++++++++++++++++ 2 files changed, 235 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 17991adc6a49..85f34878e9ad 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,4 +1,4 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -8,7 +8,9 @@ import type { TypeOrValueSpecifier } from '../util'; import { createRule, getConstrainedTypeAtLocation, + getOperatorPrecedence, getParserServices, + getWrappingFixer, isBuiltinSymbolLike, isPromiseLike, isTypeFlagSet, @@ -30,7 +32,10 @@ type MessageIds = | 'noIterableSpreadInObject' | 'noMapSpreadInObject' | 'noPromiseSpreadInObject' - | 'noStringSpread'; + | 'noStringSpread' + | 'replaceMapSpreadInObject' + | 'replacePromiseSpreadInObject' + | 'replaceStringSpread'; export default createRule({ name: 'no-misused-spread', @@ -42,6 +47,7 @@ export default createRule({ recommended: 'strict', requiresTypeChecking: true, }, + hasSuggestions: true, messages: { noArraySpreadInObject: 'Using the spread operator on an array in an object will result in a list of indices.', @@ -59,6 +65,9 @@ export default createRule({ 'Using the spread operator on Promise in an object can cause unexpected behavior. Did you forget to await the promise?', noStringSpread: "Using the spread operator on a string can cause unexpected behavior. Prefer `.split('')` instead.", + replaceMapSpreadInObject: 'replace map spread in object', + replacePromiseSpreadInObject: 'replace promise in spread', + replaceStringSpread: 'replace string spread', }, schema: [ { @@ -99,6 +108,59 @@ export default createRule({ } } + function getMapSpreadSuggestions( + node: TSESTree.Expression, + type: ts.Type, + ): TSESLint.ReportSuggestionArray | null { + const types = tsutils.unionTypeParts(type); + if (types.some(t => !isMap(services.program, t))) { + return null; + } + return [ + { + messageId: 'replaceMapSpreadInObject', + fix: getWrappingFixer({ + node, + sourceCode: context.sourceCode, + wrap: code => `Object.entries(${code})`, + }), + }, + ]; + } + + function isHigherPrecedenceThanAwait(tsNode: ts.Node): boolean { + const operator = ts.isBinaryExpression(tsNode) + ? tsNode.operatorToken.kind + : ts.SyntaxKind.Unknown; + const nodePrecedence = getOperatorPrecedence(tsNode.kind, operator); + const awaitPrecedence = getOperatorPrecedence( + ts.SyntaxKind.AwaitExpression, + ts.SyntaxKind.Unknown, + ); + return nodePrecedence > awaitPrecedence; + } + + function getPromiseSpreadSuggestions( + node: TSESTree.Expression, + ): TSESLint.ReportSuggestionArray { + const isHighPrecendence = isHigherPrecedenceThanAwait( + services.esTreeNodeToTSNodeMap.get(node), + ); + + return [ + { + messageId: 'replacePromiseSpreadInObject', + fix: fixer => + isHighPrecendence + ? fixer.insertTextBefore(node, 'await ') + : [ + fixer.insertTextBefore(node, 'await ('), + fixer.insertTextAfter(node, ')'), + ], + }, + ]; + } + function checkObjectSpread( node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement, ): void { @@ -112,6 +174,7 @@ export default createRule({ context.report({ node, messageId: 'noPromiseSpreadInObject', + suggest: getPromiseSpreadSuggestions(node.argument), }); return; @@ -130,6 +193,7 @@ export default createRule({ context.report({ node, messageId: 'noMapSpreadInObject', + suggest: getMapSpreadSuggestions(node.argument, type), }); return; 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 8701ea580daa..2323eedf6732 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -741,6 +741,19 @@ ruleTester.run('no-misused-spread', rule, { endLine: 6, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + const o = { + ...Object.entries(new Map([ + ['test-1', 1], + ['test-2', 2], + ])), + }; + `, + }, + ], }, ], }, @@ -759,6 +772,19 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 7, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + const map = new Map([ + ['test-1', 1], + ['test-2', 2], + ]); + + const o = { ...Object.entries(map) }; + `, + }, + ], }, ], }, @@ -773,6 +799,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + const o = { ...Object.entries(map) }; + `, + }, + ], }, ], }, @@ -787,6 +822,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: ReadonlyMap; + const o = { ...Object.entries(map) }; + `, + }, + ], }, ], }, @@ -801,6 +845,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: WeakMap<{ a: number }, string>; + const o = { ...Object.entries(map) }; + `, + }, + ], }, ], }, @@ -829,6 +882,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 32, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare function getMap(): Map; + const o = { ...Object.entries(getMap()) }; + `, + }, + ], }, ], }, @@ -843,6 +905,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 25, line: 3, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const a: Map & Set; + const o = { ...Object.entries(a) }; + `, + }, + ], }, ], }, @@ -871,6 +942,38 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 31, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + const promise = new Promise(() => {}); + const o = { ...await promise }; + `, + }, + ], + }, + ], + }, + { + code: ` + const promise = new Promise(() => {}); + const o = { ...(promise || {}) }; + `, + errors: [ + { + column: 21, + endColumn: 39, + line: 3, + messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + const promise = new Promise(() => {}); + const o = { ...await (promise || {}) }; + `, + }, + ], }, ], }, @@ -886,6 +989,16 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 30, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + function withPromise

>(promise: P) { + return { ...await promise }; + } + `, + }, + ], }, ], }, @@ -900,6 +1013,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 36, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + declare const maybePromise: Promise | { a: number }; + const o = { ...await maybePromise }; + `, + }, + ], }, ], }, @@ -914,6 +1036,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 31, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + declare const promise: Promise & { a: number }; + const o = { ...await promise }; + `, + }, + ], }, ], }, @@ -928,6 +1059,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 36, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + declare function getPromise(): Promise; + const o = { ...await getPromise() }; + `, + }, + ], }, ], }, @@ -942,6 +1082,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 36, line: 3, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + declare function getPromise>(arg: T): T; + const o = { ...await getPromise() }; + `, + }, + ], }, ], }, @@ -1636,6 +1785,16 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 32, line: 4, messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + + const o =

; + `, + }, + ], }, ], languageOptions: { @@ -1658,6 +1817,16 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 36, line: 4, messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'replacePromiseSpreadInObject', + output: ` + const promise = new Promise(() => {}); + + const o =
; + `, + }, + ], }, ], languageOptions: { From c17644e4e36166764b7eeca99b2f716a8499f574 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 28 Jan 2025 00:43:36 +0900 Subject: [PATCH 2/9] implement --- .../src/rules/no-misused-spread.ts | 33 ++++- .../tests/rules/no-misused-spread.test.ts | 113 +++++++++++++++--- 2 files changed, 126 insertions(+), 20 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 85f34878e9ad..8f7a806ea07b 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,5 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -25,6 +26,7 @@ type Options = [ ]; type MessageIds = + | 'addAwait' | 'noArraySpreadInObject' | 'noClassDeclarationSpreadInObject' | 'noClassInstanceSpreadInObject' @@ -34,7 +36,6 @@ type MessageIds = | 'noPromiseSpreadInObject' | 'noStringSpread' | 'replaceMapSpreadInObject' - | 'replacePromiseSpreadInObject' | 'replaceStringSpread'; export default createRule({ @@ -49,6 +50,7 @@ export default createRule({ }, hasSuggestions: true, messages: { + addAwait: 'Add await operator.', noArraySpreadInObject: 'Using the spread operator on an array in an object will result in a list of indices.', noClassDeclarationSpreadInObject: @@ -65,9 +67,9 @@ export default createRule({ 'Using the spread operator on Promise in an object can cause unexpected behavior. Did you forget to await the promise?', noStringSpread: "Using the spread operator on a string can cause unexpected behavior. Prefer `.split('')` instead.", - replaceMapSpreadInObject: 'replace map spread in object', - replacePromiseSpreadInObject: 'replace promise in spread', - replaceStringSpread: 'replace string spread', + replaceMapSpreadInObject: + 'Replace map spread in object with `Object.fromEntries(map)`', + replaceStringSpread: "Replace string spread with `.split('')`", }, schema: [ { @@ -104,6 +106,7 @@ export default createRule({ context.report({ node, messageId: 'noStringSpread', + suggest: getStringSpreadSuggestions(node, type), }); } } @@ -149,7 +152,7 @@ export default createRule({ return [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', fix: fixer => isHighPrecendence ? fixer.insertTextBefore(node, 'await ') @@ -161,6 +164,26 @@ export default createRule({ ]; } + function getStringSpreadSuggestions( + node: TSESTree.SpreadElement, + type: ts.Type, + ): TSESLint.ReportSuggestionArray | null { + if ( + node.parent.type !== AST_NODE_TYPES.ArrayExpression || + node.parent.elements.length > 1 || + tsutils.unionTypeParts(type).some(type => !isString(type)) + ) { + return null; + } + const code = context.sourceCode.getText(node.argument); + return [ + { + messageId: 'replaceStringSpread', + fix: fixer => fixer.replaceText(node.parent, `${code}.split('')`), + }, + ]; + } + function checkObjectSpread( node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement, ): void { 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 2323eedf6732..9e18c2c0c441 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -306,6 +306,12 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 21, line: 1, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: "const a = 'test'.split('');", + }, + ], }, ], }, @@ -321,6 +327,16 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 26, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + function withText(text: Text) { + return text.split(''); + } + `, + }, + ], }, ], }, @@ -335,6 +351,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + const test = 'hello'; + const a = test.split(''); + `, + }, + ], }, ], }, @@ -349,6 +374,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + const test = \`he\${'ll'}o\`; + const a = test.split(''); + `, + }, + ], }, ], }, @@ -363,6 +397,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare const test: string; + const a = test.split(''); + `, + }, + ], }, ], }, @@ -391,6 +434,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare const test: string & { __brand: 'test' }; + const a = test.split(''); + `, + }, + ], }, ], }, @@ -419,6 +471,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function getString(): string; + const a = getString().split(''); + `, + }, + ], }, ], }, @@ -490,6 +551,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function getString(): T; + const a = getString().split(''); + `, + }, + ], }, ], }, @@ -504,6 +574,15 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function getString(): string & { __brand: 'test' }; + const a = getString().split(''); + `, + }, + ], }, ], }, @@ -944,7 +1023,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` const promise = new Promise(() => {}); const o = { ...await promise }; @@ -956,21 +1035,25 @@ ruleTester.run('no-misused-spread', rule, { }, { code: ` - const promise = new Promise(() => {}); - const o = { ...(promise || {}) }; + declare const promise: Promise<{ a: 1 }>; + async function foo() { + return { ...(promise || {}) }; + } `, errors: [ { - column: 21, - endColumn: 39, - line: 3, + column: 20, + endColumn: 38, + line: 4, messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` - const promise = new Promise(() => {}); - const o = { ...await (promise || {}) }; + declare const promise: Promise<{ a: 1 }>; + async function foo() { + return { ...(await (promise || {})) }; + } `, }, ], @@ -991,7 +1074,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` function withPromise

>(promise: P) { return { ...await promise }; @@ -1015,7 +1098,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` declare const maybePromise: Promise | { a: number }; const o = { ...await maybePromise }; @@ -1038,7 +1121,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` declare const promise: Promise & { a: number }; const o = { ...await promise }; @@ -1061,7 +1144,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` declare function getPromise(): Promise; const o = { ...await getPromise() }; @@ -1084,7 +1167,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` declare function getPromise>(arg: T): T; const o = { ...await getPromise() }; @@ -1819,7 +1902,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'noPromiseSpreadInObject', suggestions: [ { - messageId: 'replacePromiseSpreadInObject', + messageId: 'addAwait', output: ` const promise = new Promise(() => {}); From e7098ddf33c73201a3e2695ee62ab4233662be6c Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 28 Jan 2025 18:27:10 +0900 Subject: [PATCH 3/9] implement --- .../src/rules/no-misused-spread.ts | 15 +++--- .../tests/rules/no-misused-spread.test.ts | 48 +++++++++++++++++++ 2 files changed, 57 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 8f7a806ea07b..059f5e26a840 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -168,18 +168,21 @@ export default createRule({ node: TSESTree.SpreadElement, type: ts.Type, ): TSESLint.ReportSuggestionArray | null { - if ( - node.parent.type !== AST_NODE_TYPES.ArrayExpression || - node.parent.elements.length > 1 || - tsutils.unionTypeParts(type).some(type => !isString(type)) - ) { + if (tsutils.unionTypeParts(type).some(type => !isString(type))) { return null; } + + const targetNode: TSESTree.Node = + node.parent.type !== AST_NODE_TYPES.ArrayExpression || + node.parent.elements.length > 1 + ? node.argument + : node.parent; + const code = context.sourceCode.getText(node.argument); return [ { messageId: 'replaceStringSpread', - fix: fixer => fixer.replaceText(node.parent, `${code}.split('')`), + fix: fixer => fixer.replaceText(targetNode, `${code}.split('')`), }, ]; } 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 9e18c2c0c441..c695b446ce0a 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -497,6 +497,18 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 29, line: 6, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function textIdentity(...args: string[]); + + declare const text: string; + + textIdentity(...text.split('')); + `, + }, + ], }, ], }, @@ -514,12 +526,36 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 29, line: 6, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function textIdentity(...args: string[]); + + declare const text: string; + + textIdentity(...text.split(''), 'and', ...text); + `, + }, + ], }, { column: 38, endColumn: 45, line: 6, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function textIdentity(...args: string[]); + + declare const text: string; + + textIdentity(...text, 'and', ...text.split('')); + `, + }, + ], }, ], }, @@ -537,6 +573,18 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 31, line: 5, messageId: 'noStringSpread', + suggestions: [ + { + messageId: 'replaceStringSpread', + output: ` + declare function textIdentity(...args: string[]); + + function withText(text: Text) { + textIdentity(...text.split('')); + } + `, + }, + ], }, ], }, From 128a7c2f143130ad99412f43c632ac5e8ef4f0ee Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 28 Jan 2025 22:29:37 +0900 Subject: [PATCH 4/9] fix --- .../src/rules/no-misused-spread.ts | 2 +- .../tests/rules/no-misused-spread.test.ts | 43 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 059f5e26a840..6457a1d4d007 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -125,7 +125,7 @@ export default createRule({ fix: getWrappingFixer({ node, sourceCode: context.sourceCode, - wrap: code => `Object.entries(${code})`, + wrap: code => `Object.fromEntries(${code})`, }), }, ]; 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 c695b446ce0a..7b3ef2b21895 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -873,7 +873,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` const o = { - ...Object.entries(new Map([ + ...Object.fromEntries(new Map([ ['test-1', 1], ['test-2', 2], ])), @@ -908,7 +908,7 @@ ruleTester.run('no-misused-spread', rule, { ['test-2', 2], ]); - const o = { ...Object.entries(map) }; + const o = { ...Object.fromEntries(map) }; `, }, ], @@ -931,7 +931,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: Map; - const o = { ...Object.entries(map) }; + const o = { ...Object.fromEntries(map) }; `, }, ], @@ -954,7 +954,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: ReadonlyMap; - const o = { ...Object.entries(map) }; + const o = { ...Object.fromEntries(map) }; `, }, ], @@ -977,7 +977,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: WeakMap<{ a: number }, string>; - const o = { ...Object.entries(map) }; + const o = { ...Object.fromEntries(map) }; `, }, ], @@ -1014,7 +1014,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare function getMap(): Map; - const o = { ...Object.entries(getMap()) }; + const o = { ...Object.fromEntries(getMap()) }; `, }, ], @@ -1037,7 +1037,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const a: Map & Set; - const o = { ...Object.entries(a) }; + const o = { ...Object.fromEntries(a) }; `, }, ], @@ -1108,6 +1108,33 @@ ruleTester.run('no-misused-spread', rule, { }, ], }, + { + code: ` + declare const promise: Promise; + async function foo() { + return { ...(Math.random() < 0.5 ? promise : {}) }; + } + `, + errors: [ + { + column: 20, + endColumn: 59, + line: 4, + messageId: 'noPromiseSpreadInObject', + suggestions: [ + { + messageId: 'addAwait', + output: ` + declare const promise: Promise; + async function foo() { + return { ...(await (Math.random() < 0.5 ? promise : {})) }; + } + `, + }, + ], + }, + ], + }, { code: ` function withPromise

>(promise: P) { @@ -1922,7 +1949,7 @@ ruleTester.run('no-misused-spread', rule, { output: ` declare const map: Map; - const o =

; + const o =
; `, }, ], From 111e56897dde3190f5f6da46df572e25b6476a30 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Fri, 14 Feb 2025 23:35:00 +0900 Subject: [PATCH 5/9] merge --- .../src/rules/no-misused-spread.ts | 29 +--- .../tests/rules/no-misused-spread.test.ts | 127 ------------------ 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 378efaae7d86..ed75c0261115 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,6 +1,5 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -35,8 +34,7 @@ type MessageIds = | 'noMapSpreadInObject' | 'noPromiseSpreadInObject' | 'noStringSpread' - | 'replaceMapSpreadInObject' - | 'replaceStringSpread'; + | 'replaceMapSpreadInObject'; export default createRule({ name: 'no-misused-spread', @@ -74,7 +72,6 @@ export default createRule({ ].join('\n'), replaceMapSpreadInObject: 'Replace map spread in object with `Object.fromEntries(map)`', - replaceStringSpread: "Replace string spread with `.split('')`", }, schema: [ { @@ -111,7 +108,6 @@ export default createRule({ context.report({ node, messageId: 'noStringSpread', - suggest: getStringSpreadSuggestions(node, type), }); } } @@ -169,29 +165,6 @@ export default createRule({ ]; } - function getStringSpreadSuggestions( - node: TSESTree.SpreadElement, - type: ts.Type, - ): TSESLint.ReportSuggestionArray | null { - if (tsutils.unionTypeParts(type).some(type => !isString(type))) { - return null; - } - - const targetNode: TSESTree.Node = - node.parent.type !== AST_NODE_TYPES.ArrayExpression || - node.parent.elements.length > 1 - ? node.argument - : node.parent; - - const code = context.sourceCode.getText(node.argument); - return [ - { - messageId: 'replaceStringSpread', - fix: fixer => fixer.replaceText(targetNode, `${code}.split('')`), - }, - ]; - } - function checkObjectSpread( node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement, ): void { 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 7b3ef2b21895..57385b82f34b 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -306,12 +306,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 21, line: 1, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: "const a = 'test'.split('');", - }, - ], }, ], }, @@ -327,16 +321,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 26, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - function withText(text: Text) { - return text.split(''); - } - `, - }, - ], }, ], }, @@ -351,15 +335,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - const test = 'hello'; - const a = test.split(''); - `, - }, - ], }, ], }, @@ -374,15 +349,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - const test = \`he\${'ll'}o\`; - const a = test.split(''); - `, - }, - ], }, ], }, @@ -397,15 +363,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare const test: string; - const a = test.split(''); - `, - }, - ], }, ], }, @@ -434,15 +391,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 27, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare const test: string & { __brand: 'test' }; - const a = test.split(''); - `, - }, - ], }, ], }, @@ -471,15 +419,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function getString(): string; - const a = getString().split(''); - `, - }, - ], }, ], }, @@ -497,18 +436,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 29, line: 6, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function textIdentity(...args: string[]); - - declare const text: string; - - textIdentity(...text.split('')); - `, - }, - ], }, ], }, @@ -526,36 +453,12 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 29, line: 6, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function textIdentity(...args: string[]); - - declare const text: string; - - textIdentity(...text.split(''), 'and', ...text); - `, - }, - ], }, { column: 38, endColumn: 45, line: 6, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function textIdentity(...args: string[]); - - declare const text: string; - - textIdentity(...text, 'and', ...text.split('')); - `, - }, - ], }, ], }, @@ -573,18 +476,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 31, line: 5, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function textIdentity(...args: string[]); - - function withText(text: Text) { - textIdentity(...text.split('')); - } - `, - }, - ], }, ], }, @@ -599,15 +490,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function getString(): T; - const a = getString().split(''); - `, - }, - ], }, ], }, @@ -622,15 +504,6 @@ ruleTester.run('no-misused-spread', rule, { endColumn: 34, line: 3, messageId: 'noStringSpread', - suggestions: [ - { - messageId: 'replaceStringSpread', - output: ` - declare function getString(): string & { __brand: 'test' }; - const a = getString().split(''); - `, - }, - ], }, ], }, From affa14b011ca72b90daacb1f26724a13630a14ef Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 16 Feb 2025 22:05:53 +0900 Subject: [PATCH 6/9] fix --- .../src/rules/no-misused-spread.ts | 30 ++++++++++++++++--- .../tests/rules/no-misused-spread.test.ts | 18 +++++------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index ed75c0261115..56bb3328532f 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,4 +1,8 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { + AST_NODE_TYPES, + type TSESLint, + type TSESTree, +} from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; @@ -113,18 +117,36 @@ export default createRule({ } function getMapSpreadSuggestions( - node: TSESTree.Expression, + node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement, type: ts.Type, ): TSESLint.ReportSuggestionArray | null { const types = tsutils.unionTypeParts(type); if (types.some(t => !isMap(services.program, t))) { return null; } + + if ( + node.parent.type === AST_NODE_TYPES.ObjectExpression && + node.parent.properties.length === 1 + ) { + return [ + { + messageId: 'replaceMapSpreadInObject', + fix: getWrappingFixer({ + node: node.parent, + innerNode: node.argument, + sourceCode: context.sourceCode, + wrap: code => `Object.fromEntries(${code})`, + }), + }, + ]; + } + return [ { messageId: 'replaceMapSpreadInObject', fix: getWrappingFixer({ - node, + node: node.argument, sourceCode: context.sourceCode, wrap: code => `Object.fromEntries(${code})`, }), @@ -197,7 +219,7 @@ export default createRule({ context.report({ node, messageId: 'noMapSpreadInObject', - suggest: getMapSpreadSuggestions(node.argument, type), + suggest: getMapSpreadSuggestions(node, type), }); return; 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 57385b82f34b..68e2d7732261 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -745,12 +745,10 @@ ruleTester.run('no-misused-spread', rule, { { messageId: 'replaceMapSpreadInObject', output: ` - const o = { - ...Object.fromEntries(new Map([ + const o = Object.fromEntries(new Map([ ['test-1', 1], ['test-2', 2], - ])), - }; + ])); `, }, ], @@ -781,7 +779,7 @@ ruleTester.run('no-misused-spread', rule, { ['test-2', 2], ]); - const o = { ...Object.fromEntries(map) }; + const o = Object.fromEntries(map); `, }, ], @@ -804,7 +802,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: Map; - const o = { ...Object.fromEntries(map) }; + const o = Object.fromEntries(map); `, }, ], @@ -827,7 +825,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: ReadonlyMap; - const o = { ...Object.fromEntries(map) }; + const o = Object.fromEntries(map); `, }, ], @@ -850,7 +848,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: WeakMap<{ a: number }, string>; - const o = { ...Object.fromEntries(map) }; + const o = Object.fromEntries(map); `, }, ], @@ -887,7 +885,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare function getMap(): Map; - const o = { ...Object.fromEntries(getMap()) }; + const o = Object.fromEntries(getMap()); `, }, ], @@ -910,7 +908,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const a: Map & Set; - const o = { ...Object.fromEntries(a) }; + const o = Object.fromEntries(a); `, }, ], From 7977d08f8dae7337783ba132ce405775101b0e37 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 16 Feb 2025 22:14:46 +0900 Subject: [PATCH 7/9] add tests --- .../src/rules/no-misused-spread.ts | 7 +-- .../tests/rules/no-misused-spread.test.ts | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index 56bb3328532f..cc28f91a10e7 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -1,9 +1,6 @@ -import { - AST_NODE_TYPES, - type TSESLint, - type TSESTree, -} from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; 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 68e2d7732261..3e64c2c58ba2 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -809,6 +809,54 @@ ruleTester.run('no-misused-spread', rule, { }, ], }, + { + code: ` + declare const map: Map; + const others = { a: 1 }; + const o = { ...map, ...a }; + `, + errors: [ + { + column: 21, + endColumn: 27, + line: 4, + messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + const others = { a: 1 }; + const o = { ...Object.fromEntries(map), ...a }; + `, + }, + ], + }, + ], + }, + { + code: ` + declare const map: Map; + const o = { a: 1, ...map }; + `, + errors: [ + { + column: 27, + endColumn: 33, + line: 3, + messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + const o = { a: 1, ...Object.fromEntries(map) }; + `, + }, + ], + }, + ], + }, { code: ` declare const map: ReadonlyMap; From 75ce541358bebcbbcc528a5ed0c9b487f7e6f48f Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Sun, 16 Feb 2025 22:21:30 +0900 Subject: [PATCH 8/9] Update no-misused-spread.test.ts --- .../tests/rules/no-misused-spread.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 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 3e64c2c58ba2..da18207824be 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -813,7 +813,7 @@ ruleTester.run('no-misused-spread', rule, { code: ` declare const map: Map; const others = { a: 1 }; - const o = { ...map, ...a }; + const o = { ...map, ...others }; `, errors: [ { @@ -827,7 +827,7 @@ ruleTester.run('no-misused-spread', rule, { output: ` declare const map: Map; const others = { a: 1 }; - const o = { ...Object.fromEntries(map), ...a }; + const o = { ...Object.fromEntries(map), ...others }; `, }, ], @@ -837,12 +837,12 @@ ruleTester.run('no-misused-spread', rule, { { code: ` declare const map: Map; - const o = { a: 1, ...map }; + const o = { other: 1, ...map }; `, errors: [ { - column: 27, - endColumn: 33, + column: 31, + endColumn: 37, line: 3, messageId: 'noMapSpreadInObject', suggestions: [ @@ -850,7 +850,7 @@ ruleTester.run('no-misused-spread', rule, { messageId: 'replaceMapSpreadInObject', output: ` declare const map: Map; - const o = { a: 1, ...Object.fromEntries(map) }; + const o = { other: 1, ...Object.fromEntries(map) }; `, }, ], From 3832ddaf0d693399807a16ba5c696921b4d307ad Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 19 Feb 2025 21:15:53 +0900 Subject: [PATCH 9/9] appy reviews --- .../src/rules/no-misused-spread.ts | 16 +------ .../eslint-plugin/src/rules/return-await.ts | 14 +----- packages/eslint-plugin/src/util/index.ts | 1 + .../src/util/isHigherPrecedenceThanAwait.ts | 15 ++++++ .../tests/rules/no-misused-spread.test.ts | 46 +++++++++++++++++++ 5 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 packages/eslint-plugin/src/util/isHigherPrecedenceThanAwait.ts diff --git a/packages/eslint-plugin/src/rules/no-misused-spread.ts b/packages/eslint-plugin/src/rules/no-misused-spread.ts index cc28f91a10e7..74b2ddb6abf5 100644 --- a/packages/eslint-plugin/src/rules/no-misused-spread.ts +++ b/packages/eslint-plugin/src/rules/no-misused-spread.ts @@ -9,7 +9,6 @@ import type { TypeOrValueSpecifier } from '../util'; import { createRule, getConstrainedTypeAtLocation, - getOperatorPrecedence, getParserServices, getWrappingFixer, isBuiltinSymbolLike, @@ -17,6 +16,7 @@ import { isTypeFlagSet, readonlynessOptionsSchema, typeMatchesSomeSpecifier, + isHigherPrecedenceThanAwait, } from '../util'; type Options = [ @@ -72,7 +72,7 @@ export default createRule({ "Otherwise, if you don't need to preserve emojis or other non-Ascii characters, disable this lint rule on this line or configure the 'allow' rule option.", ].join('\n'), replaceMapSpreadInObject: - 'Replace map spread in object with `Object.fromEntries(map)`', + 'Replace map spread in object with `Object.fromEntries()`', }, schema: [ { @@ -151,18 +151,6 @@ export default createRule({ ]; } - function isHigherPrecedenceThanAwait(tsNode: ts.Node): boolean { - const operator = ts.isBinaryExpression(tsNode) - ? tsNode.operatorToken.kind - : ts.SyntaxKind.Unknown; - const nodePrecedence = getOperatorPrecedence(tsNode.kind, operator); - const awaitPrecedence = getOperatorPrecedence( - ts.SyntaxKind.AwaitExpression, - ts.SyntaxKind.Unknown, - ); - return nodePrecedence > awaitPrecedence; - } - function getPromiseSpreadSuggestions( node: TSESTree.Expression, ): TSESLint.ReportSuggestionArray { diff --git a/packages/eslint-plugin/src/rules/return-await.ts b/packages/eslint-plugin/src/rules/return-await.ts index a040bbb9463e..0ee20eea92a8 100644 --- a/packages/eslint-plugin/src/rules/return-await.ts +++ b/packages/eslint-plugin/src/rules/return-await.ts @@ -12,8 +12,8 @@ import { isAwaitKeyword, needsToBeAwaited, nullThrows, + isHigherPrecedenceThanAwait, } from '../util'; -import { getOperatorPrecedence } from '../util/getOperatorPrecedence'; type FunctionNode = | TSESTree.ArrowFunctionExpression @@ -278,18 +278,6 @@ export default createRule({ ]; } - function isHigherPrecedenceThanAwait(node: ts.Node): boolean { - const operator = ts.isBinaryExpression(node) - ? node.operatorToken.kind - : ts.SyntaxKind.Unknown; - const nodePrecedence = getOperatorPrecedence(node.kind, operator); - const awaitPrecedence = getOperatorPrecedence( - ts.SyntaxKind.AwaitExpression, - ts.SyntaxKind.Unknown, - ); - return nodePrecedence > awaitPrecedence; - } - function test(node: TSESTree.Expression, expression: ts.Node): void { let child: ts.Node; diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index c8a0927b162b..ae9e99e4b2bb 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -26,6 +26,7 @@ export * from './types'; export * from './getConstraintInfo'; export * from './getValueOfLiteralType'; export * from './truthinessAndNullishUtils'; +export * from './isHigherPrecedenceThanAwait'; // this is done for convenience - saves migrating all of the old rules export * from '@typescript-eslint/type-utils'; diff --git a/packages/eslint-plugin/src/util/isHigherPrecedenceThanAwait.ts b/packages/eslint-plugin/src/util/isHigherPrecedenceThanAwait.ts new file mode 100644 index 000000000000..20ee6885d269 --- /dev/null +++ b/packages/eslint-plugin/src/util/isHigherPrecedenceThanAwait.ts @@ -0,0 +1,15 @@ +import * as ts from 'typescript'; + +import { getOperatorPrecedence } from './getOperatorPrecedence'; + +export function isHigherPrecedenceThanAwait(tsNode: ts.Node): boolean { + const operator = ts.isBinaryExpression(tsNode) + ? tsNode.operatorToken.kind + : ts.SyntaxKind.Unknown; + const nodePrecedence = getOperatorPrecedence(tsNode.kind, operator); + const awaitPrecedence = getOperatorPrecedence( + ts.SyntaxKind.AwaitExpression, + ts.SyntaxKind.Unknown, + ); + return nodePrecedence > awaitPrecedence; +} 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 da18207824be..e187a85a1975 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-spread.test.ts @@ -809,6 +809,52 @@ ruleTester.run('no-misused-spread', rule, { }, ], }, + { + code: noFormat` + declare const map: Map; + const o = { ...(map) }; + `, + errors: [ + { + column: 21, + endColumn: 29, + line: 3, + messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + const o = Object.fromEntries(map); + `, + }, + ], + }, + ], + }, + { + code: ` + declare const map: Map; + const o = { ...(map, map) }; + `, + errors: [ + { + column: 21, + endColumn: 34, + line: 3, + messageId: 'noMapSpreadInObject', + suggestions: [ + { + messageId: 'replaceMapSpreadInObject', + output: ` + declare const map: Map; + const o = Object.fromEntries((map, map)); + `, + }, + ], + }, + ], + }, { code: ` declare const map: Map; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy