From e2f2e7f6372e65354e0af37e880d35bb87c82443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Fri, 15 Mar 2024 01:02:32 +0900 Subject: [PATCH 01/12] fix: precedence concider with parentheses --- .../src/rules/no-useless-template-literals.ts | 26 ++++++++----------- .../no-useless-template-literals.test.ts | 11 +++++++- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 4610d406465a..80bf897917cc 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -7,6 +7,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticStringValue, + getWrappingFixer, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; @@ -92,21 +93,16 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', - fix(fixer): TSESLint.RuleFix[] { - const [prevQuasi, nextQuasi] = node.quasis; - - // Remove the quasis and backticks. - return [ - fixer.removeRange([ - prevQuasi.range[1] - 3, - node.expressions[0].range[0], - ]), - - fixer.removeRange([ - node.expressions[0].range[1], - nextQuasi.range[0] + 2, - ]), - ]; + fix(fixer): TSESLint.RuleFix { + const replaceResult = getWrappingFixer({ + sourceCode: context.sourceCode, + node: node.expressions[0], + wrap: (...code: string[]) => { + return code.join(''); + }, + })(fixer) as TSESLint.RuleFix; + + return fixer.replaceText(node, replaceResult.text); }, }); diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts index d443c4ff729d..f36ffbddfc4a 100644 --- a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts +++ b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts @@ -207,7 +207,7 @@ ruleTester.run('no-useless-template-literals', rule, { { code: noFormat`\`\${ 'a' + 'b' }\`;`, - output: `'a' + 'b';`, + output: `('a' + 'b');`, errors: [ { messageId: 'noUselessTemplateLiteral', @@ -637,5 +637,14 @@ declare const nested: string, interpolation: string; }, ], }, + { + code: "true ? `${'test' || ''}`.trim() : undefined;", + output: "true ? ('test' || '').trim() : undefined;", + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, ], }); From f4c697e440e96d83b2009622098799dda920447f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Wed, 27 Mar 2024 23:07:06 +0900 Subject: [PATCH 02/12] refactor: seperate replaceNodeText fn --- .../src/rules/no-useless-template-literals.ts | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 80bf897917cc..7848cb6b1c50 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -14,6 +14,25 @@ import { type MessageId = 'noUselessTemplateLiteral'; +function getReplaceNodeText( + context: Readonly>, + node: TSESTree.TemplateLiteral, + fixer: TSESLint.RuleFixer, +): string | null { + const replaceResult = getWrappingFixer({ + sourceCode: context.sourceCode, + node: node.expressions[0], + wrap: (...code: string[]) => { + return code.join(''); + }, + })(fixer); + + if (replaceResult != null && 'text' in replaceResult) { + return replaceResult.text; + } + return null; +} + export default createRule<[], MessageId>({ name: 'no-useless-template-literals', meta: { @@ -93,16 +112,12 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', - fix(fixer): TSESLint.RuleFix { - const replaceResult = getWrappingFixer({ - sourceCode: context.sourceCode, - node: node.expressions[0], - wrap: (...code: string[]) => { - return code.join(''); - }, - })(fixer) as TSESLint.RuleFix; - - return fixer.replaceText(node, replaceResult.text); + fix(fixer): TSESLint.RuleFix | null { + const replaceText = getReplaceNodeText(context, node, fixer); + if (replaceText != null) { + return fixer.replaceText(node, replaceText); + } + return null; }, }); From 5a9f63085e1a3b08ae1c63e29deb34ca93b5165d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Wed, 10 Apr 2024 15:40:57 +0900 Subject: [PATCH 03/12] refactor: make getWrappingCode fn --- .../src/rules/no-useless-template-literals.ts | 34 +++----- .../src/util/getWrappingFixer.ts | 78 ++++++++++--------- 2 files changed, 52 insertions(+), 60 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 7848cb6b1c50..0438793d947d 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -7,32 +7,13 @@ import { getConstrainedTypeAtLocation, getParserServices, getStaticStringValue, - getWrappingFixer, + getWrappingCode, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; type MessageId = 'noUselessTemplateLiteral'; -function getReplaceNodeText( - context: Readonly>, - node: TSESTree.TemplateLiteral, - fixer: TSESLint.RuleFixer, -): string | null { - const replaceResult = getWrappingFixer({ - sourceCode: context.sourceCode, - node: node.expressions[0], - wrap: (...code: string[]) => { - return code.join(''); - }, - })(fixer); - - if (replaceResult != null && 'text' in replaceResult) { - return replaceResult.text; - } - return null; -} - export default createRule<[], MessageId>({ name: 'no-useless-template-literals', meta: { @@ -113,11 +94,14 @@ export default createRule<[], MessageId>({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', fix(fixer): TSESLint.RuleFix | null { - const replaceText = getReplaceNodeText(context, node, fixer); - if (replaceText != null) { - return fixer.replaceText(node, replaceText); - } - return null; + const wrappingCode = getWrappingCode({ + sourceCode: context.sourceCode, + node: node.expressions[0], + wrap: (...code: string[]) => { + return code.join(''); + }, + }); + return fixer.replaceText(node, wrappingCode); }, }); diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index d5d07b6ba7e1..04807dd75d9c 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -27,51 +27,59 @@ interface WrappingFixerParams { /** * Wraps node with some code. Adds parenthesis as necessary. - * @returns Fixer which adds the specified code and parens if necessary. + * @returns Code which adds the specified code and parens if necessary. */ -export function getWrappingFixer( - params: WrappingFixerParams, -): TSESLint.ReportFixFunction { +export function getWrappingCode(params: WrappingFixerParams): string { const { sourceCode, node, innerNode = node, wrap } = params; const innerNodes = Array.isArray(innerNode) ? innerNode : [innerNode]; - return (fixer): TSESLint.RuleFix => { - const innerCodes = innerNodes.map(innerNode => { - let code = sourceCode.getText(innerNode); - - /** - * Wrap our node in parens to prevent the following cases: - * - It has a weaker precedence than the code we are wrapping it in - * - It's gotten mistaken as block statement instead of object expression - */ - if ( - !isStrongPrecedenceNode(innerNode) || - isObjectExpressionInOneLineReturn(node, innerNode) - ) { - code = `(${code})`; - } + const innerCodes = innerNodes.map(innerNode => { + let code = sourceCode.getText(innerNode); + + /** + * Wrap our node in parens to prevent the following cases: + * - It has a weaker precedence than the code we are wrapping it in + * - It's gotten mistaken as block statement instead of object expression + */ + if ( + !isStrongPrecedenceNode(innerNode) || + isObjectExpressionInOneLineReturn(node, innerNode) + ) { + code = `(${code})`; + } - return code; - }); + return code; + }); - // do the wrapping - let code = wrap(...innerCodes); + // do the wrapping + let code = wrap(...innerCodes); - // check the outer expression's precedence - if (isWeakPrecedenceParent(node)) { - // we wrapped the node in some expression which very likely has a different precedence than original wrapped node - // let's wrap the whole expression in parens just in case - if (!ASTUtils.isParenthesized(node, sourceCode)) { - code = `(${code})`; - } + // check the outer expression's precedence + if (isWeakPrecedenceParent(node)) { + // we wrapped the node in some expression which very likely has a different precedence than original wrapped node + // let's wrap the whole expression in parens just in case + if (!ASTUtils.isParenthesized(node, sourceCode)) { + code = `(${code})`; } + } - // check if we need to insert semicolon - if (/^[`([]/.exec(code) && isMissingSemicolonBefore(node, sourceCode)) { - code = `;${code}`; - } + // check if we need to insert semicolon + if (/^[`([]/.exec(code) && isMissingSemicolonBefore(node, sourceCode)) { + code = `;${code}`; + } + + return code; +} - return fixer.replaceText(node, code); +/** + * Wraps node with some code. Adds parenthesis as necessary. + * @returns Fixer which adds the specified code and parens if necessary. + */ +export function getWrappingFixer( + params: WrappingFixerParams, +): TSESLint.ReportFixFunction { + return (fixer): TSESLint.RuleFix => { + return fixer.replaceText(params.node, getWrappingCode(params)); }; } From 67f19bd5ce80f0ea1c4058066395750d69c5499e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 00:39:02 +0900 Subject: [PATCH 04/12] fix: make new fn --- .../src/rules/no-useless-template-literals.ts | 9 +- .../src/util/getWrappingFixer.ts | 94 ++++++++++--------- .../no-useless-template-literals.test.ts | 2 +- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 0438793d947d..b39a9a20295d 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -8,6 +8,7 @@ import { getParserServices, getStaticStringValue, getWrappingCode, + getWrappingFixer, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; @@ -96,11 +97,11 @@ export default createRule<[], MessageId>({ fix(fixer): TSESLint.RuleFix | null { const wrappingCode = getWrappingCode({ sourceCode: context.sourceCode, - node: node.expressions[0], - wrap: (...code: string[]) => { - return code.join(''); - }, + replaceNode: node.expressions[0], + originNode:node, + parent:node.parent }); + return fixer.replaceText(node, wrappingCode); }, }); diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index 04807dd75d9c..796288f9299e 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -27,60 +27,67 @@ interface WrappingFixerParams { /** * Wraps node with some code. Adds parenthesis as necessary. - * @returns Code which adds the specified code and parens if necessary. + * @returns Fixer which adds the specified code and parens if necessary. */ -export function getWrappingCode(params: WrappingFixerParams): string { +export function getWrappingFixer( + params: WrappingFixerParams, +): TSESLint.ReportFixFunction { const { sourceCode, node, innerNode = node, wrap } = params; const innerNodes = Array.isArray(innerNode) ? innerNode : [innerNode]; - const innerCodes = innerNodes.map(innerNode => { - let code = sourceCode.getText(innerNode); - - /** - * Wrap our node in parens to prevent the following cases: - * - It has a weaker precedence than the code we are wrapping it in - * - It's gotten mistaken as block statement instead of object expression - */ - if ( - !isStrongPrecedenceNode(innerNode) || - isObjectExpressionInOneLineReturn(node, innerNode) - ) { - code = `(${code})`; - } + return (fixer): TSESLint.RuleFix => { + const innerCodes = innerNodes.map(innerNode => { + let code = sourceCode.getText(innerNode); + + /** + * Wrap our node in parens to prevent the following cases: + * - It has a weaker precedence than the code we are wrapping it in + * - It's gotten mistaken as block statement instead of object expression + */ + if ( + !isStrongPrecedenceNode(innerNode) || + isObjectExpressionInOneLineReturn(node, innerNode) + ) { + code = `(${code})`; + } - return code; - }); + return code; + }); - // do the wrapping - let code = wrap(...innerCodes); + // do the wrapping + let code = wrap(...innerCodes); - // check the outer expression's precedence - if (isWeakPrecedenceParent(node)) { - // we wrapped the node in some expression which very likely has a different precedence than original wrapped node - // let's wrap the whole expression in parens just in case - if (!ASTUtils.isParenthesized(node, sourceCode)) { - code = `(${code})`; + // check the outer expression's precedence + if (isWeakPrecedenceParent(node)) { + // we wrapped the node in some expression which very likely has a different precedence than original wrapped node + // let's wrap the whole expression in parens just in case + if (!ASTUtils.isParenthesized(node, sourceCode)) { + code = `(${code})`; + } } - } - // check if we need to insert semicolon - if (/^[`([]/.exec(code) && isMissingSemicolonBefore(node, sourceCode)) { - code = `;${code}`; - } + // check if we need to insert semicolon + if (/^[`([]/.exec(code) && isMissingSemicolonBefore(node, sourceCode)) { + code = `;${code}`; + } - return code; + return fixer.replaceText(node, code); + }; } -/** - * Wraps node with some code. Adds parenthesis as necessary. - * @returns Fixer which adds the specified code and parens if necessary. - */ -export function getWrappingFixer( - params: WrappingFixerParams, -): TSESLint.ReportFixFunction { - return (fixer): TSESLint.RuleFix => { - return fixer.replaceText(params.node, getWrappingCode(params)); - }; +export function getWrappingCode(params:{ + sourceCode: Readonly; + replaceNode: TSESTree.Node; + originNode:TSESTree.Node; + parent: TSESTree.Node; +}){ + const { sourceCode, replaceNode, originNode, parent } = params; + const code = sourceCode.getText(replaceNode); + const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode) + const isParentNeedParam = isWeakPrecedenceParent(originNode, parent) + + if(isNodeNeedParen && isParentNeedParam) return `(${code})`; + return code } /** @@ -105,9 +112,8 @@ export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean { /** * Check if a node's parent could have different precedence if the node changes. */ -function isWeakPrecedenceParent(node: TSESTree.Node): boolean { +function isWeakPrecedenceParent(node: TSESTree.Node, parent = node.parent!): boolean { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const parent = node.parent!; if ( parent.type === AST_NODE_TYPES.UpdateExpression || diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts index f36ffbddfc4a..4e316594c3b7 100644 --- a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts +++ b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts @@ -207,7 +207,7 @@ ruleTester.run('no-useless-template-literals', rule, { { code: noFormat`\`\${ 'a' + 'b' }\`;`, - output: `('a' + 'b');`, + output: `'a' + 'b';`, errors: [ { messageId: 'noUselessTemplateLiteral', From 92db97f912640ca36a2f7780217a8d4021ea3c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 00:40:19 +0900 Subject: [PATCH 05/12] fix: revert getWrappingFixer and make getWrappingCode --- .../src/rules/no-useless-template-literals.ts | 4 ++-- .../src/util/getWrappingFixer.ts | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index b39a9a20295d..df511c73f810 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -98,8 +98,8 @@ export default createRule<[], MessageId>({ const wrappingCode = getWrappingCode({ sourceCode: context.sourceCode, replaceNode: node.expressions[0], - originNode:node, - parent:node.parent + originNode: node, + parent: node.parent, }); return fixer.replaceText(node, wrappingCode); diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index 796288f9299e..67b30ac68a63 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -75,19 +75,19 @@ export function getWrappingFixer( }; } -export function getWrappingCode(params:{ +export function getWrappingCode(params: { sourceCode: Readonly; replaceNode: TSESTree.Node; - originNode:TSESTree.Node; + originNode: TSESTree.Node; parent: TSESTree.Node; -}){ +}) { const { sourceCode, replaceNode, originNode, parent } = params; const code = sourceCode.getText(replaceNode); - const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode) - const isParentNeedParam = isWeakPrecedenceParent(originNode, parent) + const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode); + const isParentNeedParam = isWeakPrecedenceParent(originNode, parent); - if(isNodeNeedParen && isParentNeedParam) return `(${code})`; - return code + if (isNodeNeedParen && isParentNeedParam) return `(${code})`; + return code; } /** @@ -112,7 +112,10 @@ export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean { /** * Check if a node's parent could have different precedence if the node changes. */ -function isWeakPrecedenceParent(node: TSESTree.Node, parent = node.parent!): boolean { +function isWeakPrecedenceParent( + node: TSESTree.Node, + parent = node.parent!, +): boolean { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion if ( From 28ca1d08184ab2665965a08e3c42c091ba2943c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 02:39:10 +0900 Subject: [PATCH 06/12] fix: lint error fix --- .../src/rules/no-useless-template-literals.ts | 1 - packages/eslint-plugin/src/util/getWrappingFixer.ts | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index df511c73f810..80de6c6da150 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -8,7 +8,6 @@ import { getParserServices, getStaticStringValue, getWrappingCode, - getWrappingFixer, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index 67b30ac68a63..dfbd7bb6e778 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -80,13 +80,15 @@ export function getWrappingCode(params: { replaceNode: TSESTree.Node; originNode: TSESTree.Node; parent: TSESTree.Node; -}) { +}): string { const { sourceCode, replaceNode, originNode, parent } = params; const code = sourceCode.getText(replaceNode); const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode); const isParentNeedParam = isWeakPrecedenceParent(originNode, parent); - if (isNodeNeedParen && isParentNeedParam) return `(${code})`; + if (isNodeNeedParen && isParentNeedParam){ + return `(${code})`; + } return code; } @@ -114,9 +116,9 @@ export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean { */ function isWeakPrecedenceParent( node: TSESTree.Node, - parent = node.parent!, + parent = node.parent, ): boolean { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + if(!parent) return false if ( parent.type === AST_NODE_TYPES.UpdateExpression || From 8290e5a076bacbddd753311bc5933d3c45b9335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 02:39:30 +0900 Subject: [PATCH 07/12] fix: lint error fix --- packages/eslint-plugin/src/util/getWrappingFixer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index dfbd7bb6e778..ba4c68a7b5f6 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -86,7 +86,7 @@ export function getWrappingCode(params: { const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode); const isParentNeedParam = isWeakPrecedenceParent(originNode, parent); - if (isNodeNeedParen && isParentNeedParam){ + if (isNodeNeedParen && isParentNeedParam) { return `(${code})`; } return code; @@ -118,7 +118,7 @@ function isWeakPrecedenceParent( node: TSESTree.Node, parent = node.parent, ): boolean { - if(!parent) return false + if (!parent) return false; if ( parent.type === AST_NODE_TYPES.UpdateExpression || From 90e8748b1e015822062c428f554fa9611465ba12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 02:58:48 +0900 Subject: [PATCH 08/12] fix: lint error fix --- packages/eslint-plugin/src/util/getWrappingFixer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index ba4c68a7b5f6..44a2e60f94f0 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -118,7 +118,9 @@ function isWeakPrecedenceParent( node: TSESTree.Node, parent = node.parent, ): boolean { - if (!parent) return false; + if (!parent){ + return false; + } if ( parent.type === AST_NODE_TYPES.UpdateExpression || From 0bd9dcd0a8141b2b3d75f1bfe1b7ee9993e1c39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 13 May 2024 02:58:59 +0900 Subject: [PATCH 09/12] fix: lint error fix --- packages/eslint-plugin/src/util/getWrappingFixer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index 44a2e60f94f0..8f133fcc5c1c 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -118,7 +118,7 @@ function isWeakPrecedenceParent( node: TSESTree.Node, parent = node.parent, ): boolean { - if (!parent){ + if (!parent) { return false; } From d7fc54f468d81ba965da5840a119e8f2c57a8d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 14 Jul 2024 21:13:33 +0900 Subject: [PATCH 10/12] refactor: change getMovedNodeCode function --- .../src/rules/no-useless-template-literals.ts | 9 ++--- .../src/util/getWrappingFixer.ts | 40 +++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 80de6c6da150..f0df5f9ad601 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -5,9 +5,9 @@ import * as ts from 'typescript'; import { createRule, getConstrainedTypeAtLocation, + getMovedNodeCode, getParserServices, getStaticStringValue, - getWrappingCode, isTypeFlagSet, isUndefinedIdentifier, } from '../util'; @@ -94,11 +94,10 @@ export default createRule<[], MessageId>({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', fix(fixer): TSESLint.RuleFix | null { - const wrappingCode = getWrappingCode({ + const wrappingCode = getMovedNodeCode({ sourceCode: context.sourceCode, - replaceNode: node.expressions[0], - originNode: node, - parent: node.parent, + nodeToMove: node.expressions[0], + destinationNode: node, }); return fixer.replaceText(node, wrappingCode); diff --git a/packages/eslint-plugin/src/util/getWrappingFixer.ts b/packages/eslint-plugin/src/util/getWrappingFixer.ts index 8f133fcc5c1c..442cb80a2106 100644 --- a/packages/eslint-plugin/src/util/getWrappingFixer.ts +++ b/packages/eslint-plugin/src/util/getWrappingFixer.ts @@ -74,22 +74,32 @@ export function getWrappingFixer( return fixer.replaceText(node, code); }; } - -export function getWrappingCode(params: { +/** + * If the node to be moved and the destination node require parentheses, include parentheses in the node to be moved. + * @param sourceCode Source code of current file + * @param nodeToMove Nodes that need to be moved + * @param destinationNode Final destination node with nodeToMove + * @returns If parentheses are required, code for the nodeToMove node is returned with parentheses at both ends of the code. + */ +export function getMovedNodeCode(params: { sourceCode: Readonly; - replaceNode: TSESTree.Node; - originNode: TSESTree.Node; - parent: TSESTree.Node; + nodeToMove: TSESTree.Node; + destinationNode: TSESTree.Node; }): string { - const { sourceCode, replaceNode, originNode, parent } = params; - const code = sourceCode.getText(replaceNode); - const isNodeNeedParen = !isStrongPrecedenceNode(replaceNode); - const isParentNeedParam = isWeakPrecedenceParent(originNode, parent); + const { sourceCode, nodeToMove: existingNode, destinationNode } = params; + const code = sourceCode.getText(existingNode); + if (isStrongPrecedenceNode(existingNode)) { + // Moved node never needs parens + return code; + } - if (isNodeNeedParen && isParentNeedParam) { - return `(${code})`; + if (!isWeakPrecedenceParent(destinationNode)) { + // Destination would never needs parens, regardless what node moves there + return code; } - return code; + + // Parens may be necessary + return `(${code})`; } /** @@ -114,10 +124,8 @@ export function isStrongPrecedenceNode(innerNode: TSESTree.Node): boolean { /** * Check if a node's parent could have different precedence if the node changes. */ -function isWeakPrecedenceParent( - node: TSESTree.Node, - parent = node.parent, -): boolean { +function isWeakPrecedenceParent(node: TSESTree.Node): boolean { + const parent = node.parent; if (!parent) { return false; } From 4bb0876b22c09d1ef30029c2c6dfbed438531e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 18 Aug 2024 23:41:56 +0900 Subject: [PATCH 11/12] fix: confict resolve --- .../src/rules/no-useless-template-literals.ts | 169 ----- .../no-useless-template-literals.test.ts | 650 ------------------ 2 files changed, 819 deletions(-) delete mode 100644 packages/eslint-plugin/src/rules/no-useless-template-literals.ts delete mode 100644 packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts deleted file mode 100644 index f0df5f9ad601..000000000000 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; - -import { - createRule, - getConstrainedTypeAtLocation, - getMovedNodeCode, - getParserServices, - getStaticStringValue, - isTypeFlagSet, - isUndefinedIdentifier, -} from '../util'; - -type MessageId = 'noUselessTemplateLiteral'; - -export default createRule<[], MessageId>({ - name: 'no-useless-template-literals', - meta: { - fixable: 'code', - type: 'suggestion', - docs: { - description: 'Disallow unnecessary template literals', - recommended: 'strict', - requiresTypeChecking: true, - }, - messages: { - noUselessTemplateLiteral: - 'Template literal expression is unnecessary and can be simplified.', - }, - schema: [], - }, - defaultOptions: [], - create(context) { - const services = getParserServices(context); - - function isUnderlyingTypeString( - expression: TSESTree.Expression, - ): expression is TSESTree.StringLiteral | TSESTree.Identifier { - const type = getConstrainedTypeAtLocation(services, expression); - - const isString = (t: ts.Type): boolean => { - return isTypeFlagSet(t, ts.TypeFlags.StringLike); - }; - - if (type.isUnion()) { - return type.types.every(isString); - } - - if (type.isIntersection()) { - return type.types.some(isString); - } - - return isString(type); - } - - function isLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.Literal; - } - - function isTemplateLiteral(expression: TSESTree.Expression): boolean { - return expression.type === AST_NODE_TYPES.TemplateLiteral; - } - - function isInfinityIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'Infinity' - ); - } - - function isNaNIdentifier(expression: TSESTree.Expression): boolean { - return ( - expression.type === AST_NODE_TYPES.Identifier && - expression.name === 'NaN' - ); - } - - return { - TemplateLiteral(node: TSESTree.TemplateLiteral): void { - if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) { - return; - } - - const hasSingleStringVariable = - node.quasis.length === 2 && - node.quasis[0].value.raw === '' && - node.quasis[1].value.raw === '' && - node.expressions.length === 1 && - isUnderlyingTypeString(node.expressions[0]); - - if (hasSingleStringVariable) { - context.report({ - node: node.expressions[0], - messageId: 'noUselessTemplateLiteral', - fix(fixer): TSESLint.RuleFix | null { - const wrappingCode = getMovedNodeCode({ - sourceCode: context.sourceCode, - nodeToMove: node.expressions[0], - destinationNode: node, - }); - - return fixer.replaceText(node, wrappingCode); - }, - }); - - return; - } - - const fixableExpressions = node.expressions.filter( - expression => - isLiteral(expression) || - isTemplateLiteral(expression) || - isUndefinedIdentifier(expression) || - isInfinityIdentifier(expression) || - isNaNIdentifier(expression), - ); - - fixableExpressions.forEach(expression => { - context.report({ - node: expression, - messageId: 'noUselessTemplateLiteral', - fix(fixer): TSESLint.RuleFix[] { - const index = node.expressions.indexOf(expression); - const prevQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - - // Remove the quasis' parts that are related to the current expression. - const fixes = [ - fixer.removeRange([ - prevQuasi.range[1] - 2, - expression.range[0], - ]), - - fixer.removeRange([ - expression.range[1], - nextQuasi.range[0] + 1, - ]), - ]; - - const stringValue = getStaticStringValue(expression); - - if (stringValue != null) { - const escapedValue = stringValue.replace(/([`$\\])/g, '\\$1'); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } else if (isTemplateLiteral(expression)) { - // Note that some template literals get handled in the previous branch too. - // Remove the beginning and trailing backtick characters. - fixes.push( - fixer.removeRange([ - expression.range[0], - expression.range[0] + 1, - ]), - fixer.removeRange([ - expression.range[1] - 1, - expression.range[1], - ]), - ); - } - - return fixes; - }, - }); - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts deleted file mode 100644 index 4e316594c3b7..000000000000 --- a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts +++ /dev/null @@ -1,650 +0,0 @@ -import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; - -import rule from '../../src/rules/no-useless-template-literals'; -import { getFixturesRootDir } from '../RuleTester'; - -const rootPath = getFixturesRootDir(); - -const ruleTester = new RuleTester({ - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: rootPath, - project: './tsconfig.json', - }, -}); - -ruleTester.run('no-useless-template-literals', rule, { - valid: [ - "const string = 'a';", - 'const string = `a`;', - ` - declare const string: 'a'; - \`\${string}b\`; - `, - - ` - declare const number: 1; - \`\${number}b\`; - `, - - ` - declare const boolean: true; - \`\${boolean}b\`; - `, - - ` - declare const nullish: null; - \`\${nullish}-undefined\`; - `, - - ` - declare const undefinedish: undefined; - \`\${undefinedish}\`; - `, - - ` - declare const left: 'a'; - declare const right: 'b'; - \`\${left}\${right}\`; - `, - - ` - declare const left: 'a'; - declare const right: 'c'; - \`\${left}b\${right}\`; - `, - - ` - declare const left: 'a'; - declare const center: 'b'; - declare const right: 'c'; - \`\${left}\${center}\${right}\`; - `, - - '`1 + 1 = ${1 + 1}`;', - - '`true && false = ${true && false}`;', - - "tag`${'a'}${'b'}`;", - - '`${function () {}}`;', - - '`${() => {}}`;', - - '`${(...args: any[]) => args}`;', - - ` - declare const number: 1; - \`\${number}\`; - `, - - ` - declare const boolean: true; - \`\${boolean}\`; - `, - - ` - declare const nullish: null; - \`\${nullish}\`; - `, - - ` - declare const union: string | number; - \`\${union}\`; - `, - - ` - declare const unknown: unknown; - \`\${unknown}\`; - `, - - ` - declare const never: never; - \`\${never}\`; - `, - - ` - declare const any: any; - \`\${any}\`; - `, - - ` - function func(arg: T) { - \`\${arg}\`; - } - `, - - ` - \`with - - new line\`; - `, - - ` - declare const a: 'a'; - - \`\${a} with - - new line\`; - `, - - noFormat` - \`with windows \r new line\`; - `, - - ` -\`not a useless \${String.raw\`nested interpolation \${a}\`}\`; - `, - ], - - invalid: [ - { - code: '`${1}`;', - output: '`1`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 5, - }, - ], - }, - { - code: '`${1n}`;', - output: '`1`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 6, - }, - ], - }, - { - code: '`${/a/}`;', - output: '`/a/`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 7, - }, - ], - }, - - { - code: noFormat`\`\${ 1 }\`;`, - output: '`1`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: noFormat`\`\${ 'a' }\`;`, - output: `'a';`, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: noFormat`\`\${ "a" }\`;`, - output: `"a";`, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: noFormat`\`\${ 'a' + 'b' }\`;`, - output: `'a' + 'b';`, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: '`${true}`;', - output: '`true`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 8, - }, - ], - }, - - { - code: noFormat`\`\${ true }\`;`, - output: '`true`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: '`${null}`;', - output: '`null`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 8, - }, - ], - }, - - { - code: noFormat`\`\${ null }\`;`, - output: '`null`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: '`${undefined}`;', - output: '`undefined`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 13, - }, - ], - }, - - { - code: noFormat`\`\${ undefined }\`;`, - output: '`undefined`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: '`${Infinity}`;', - output: '`Infinity`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 12, - }, - ], - }, - - { - code: '`${NaN}`;', - output: '`NaN`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 7, - }, - ], - }, - - { - code: "`${'a'} ${'b'}`;", - output: '`a b`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 7, - }, - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 11, - endColumn: 14, - }, - ], - }, - - { - code: noFormat`\`\${ 'a' } \${ 'b' }\`;`, - output: '`a b`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: ` - declare const b: 'b'; - \`a\${b}\${'c'}\`; - `, - output: ` - declare const b: 'b'; - \`a\${b}c\`; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 3, - column: 17, - endColumn: 20, - }, - ], - }, - - { - code: "`use${'less'}`;", - output: '`useless`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - }, - ], - }, - - { - code: '`use${`less`}`;', - output: '`useless`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - }, - ], - }, - - { - code: ` -declare const nested: string, interpolation: string; -\`use\${\`less\${nested}\${interpolation}\`}\`; - `, - output: ` -declare const nested: string, interpolation: string; -\`useless\${nested}\${interpolation}\`; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: noFormat` -\`u\${ - // hopefully this comment is not needed. - 'se' - -}\${ - \`le\${ \`ss\` }\` -}\`; - `, - output: ` -\`use\${ - \`less\` -}\`; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 4, - }, - { - messageId: 'noUselessTemplateLiteral', - line: 7, - column: 3, - endLine: 7, - }, - { - messageId: 'noUselessTemplateLiteral', - line: 7, - column: 10, - endLine: 7, - }, - ], - }, - { - code: noFormat` -\`use\${ - \`less\` -}\`; - `, - output: ` -\`useless\`; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 3, - column: 3, - endColumn: 9, - }, - ], - }, - - { - code: "`${'1 + 1 ='} ${2}`;", - output: '`1 + 1 = 2`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 13, - }, - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 17, - endColumn: 18, - }, - ], - }, - - { - code: "`${'a'} ${true}`;", - output: '`a true`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 7, - }, - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 11, - endColumn: 15, - }, - ], - }, - - { - code: ` - declare const string: 'a'; - \`\${string}\`; - `, - output: ` - declare const string: 'a'; - string; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 3, - column: 12, - endColumn: 18, - }, - ], - }, - - { - code: noFormat` - declare const string: 'a'; - \`\${ string }\`; - `, - output: ` - declare const string: 'a'; - string; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: "`${String(Symbol.for('test'))}`;", - output: "String(Symbol.for('test'));", - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 30, - }, - ], - }, - - { - code: ` - declare const intersection: string & { _brand: 'test-brand' }; - \`\${intersection}\`; - `, - output: ` - declare const intersection: string & { _brand: 'test-brand' }; - intersection; - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 3, - column: 12, - endColumn: 24, - }, - ], - }, - - { - code: ` - function func(arg: T) { - \`\${arg}\`; - } - `, - output: ` - function func(arg: T) { - arg; - } - `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - line: 3, - column: 14, - endColumn: 17, - }, - ], - }, - - { - code: "`${'`'}`;", - output: "'`';", - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: "`back${'`'}tick`;", - output: '`back\\`tick`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: "`dollar${'${`this is test`}'}sign`;", - output: '`dollar\\${\\`this is test\\`}sign`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: '`complex${\'`${"`${test}`"}`\'}case`;', - output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: "`some ${'\\\\${test}'} string`;", - output: '`some \\\\\\${test} string`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - - { - code: "`some ${'\\\\`'} string`;", - output: '`some \\\\\\` string`;', - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - { - code: "true ? `${'test' || ''}`.trim() : undefined;", - output: "true ? ('test' || '').trim() : undefined;", - errors: [ - { - messageId: 'noUselessTemplateLiteral', - }, - ], - }, - ], -}); From e811164ba91c2a97ab80c402a0a33e5c9f3985f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 18 Aug 2024 23:53:44 +0900 Subject: [PATCH 12/12] feat: copy work to no-unnessary-template-expression --- .../no-unnecessary-template-expression.ts | 24 +++++++------------ ...no-unnecessary-template-expression.test.ts | 9 +++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts index de0ba4bf310b..606d4ae0e3eb 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -5,6 +5,7 @@ import * as ts from 'typescript'; import { createRule, getConstrainedTypeAtLocation, + getMovedNodeCode, getParserServices, isTypeFlagSet, isUndefinedIdentifier, @@ -106,21 +107,14 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUnnecessaryTemplateExpression', - fix(fixer): TSESLint.RuleFix[] { - const [prevQuasi, nextQuasi] = node.quasis; - - // Remove the quasis and backticks. - return [ - fixer.removeRange([ - prevQuasi.range[1] - 3, - node.expressions[0].range[0], - ]), - - fixer.removeRange([ - node.expressions[0].range[1], - nextQuasi.range[0] + 2, - ]), - ]; + fix(fixer): TSESLint.RuleFix | null { + const wrappingCode = getMovedNodeCode({ + sourceCode: context.sourceCode, + nodeToMove: node.expressions[0], + destinationNode: node, + }); + + return fixer.replaceText(node, wrappingCode); }, }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts index 1dd79982f50f..3902b404d9ec 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-template-expression.test.ts @@ -1127,5 +1127,14 @@ declare const nested: string, interpolation: string; }, ], }, + { + code: "true ? `${'test' || ''}`.trim() : undefined;", + output: "true ? ('test' || '').trim() : undefined;", + errors: [ + { + messageId: 'noUnnecessaryTemplateExpression', + }, + ], + }, ], });