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', + }, + ], + }, ], }); 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