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 1eb00c2872ba..90ad42fb0169 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -10,6 +10,8 @@ import { getParserServices, isTypeFlagSet, isUndefinedIdentifier, + nullThrows, + NullThrowsReasons, } from '../util'; import { rangeToLoc } from '../util/rangeToLoc'; @@ -92,6 +94,22 @@ export default createRule<[], MessageId>({ ); } + function hasCommentsBetweenQuasi( + startQuasi: TSESTree.TemplateElement, + endQuasi: TSESTree.TemplateElement, + ): boolean { + const startToken = nullThrows( + context.sourceCode.getTokenByRangeStart(startQuasi.range[0]), + NullThrowsReasons.MissingToken('`${', 'opening template literal'), + ); + const endToken = nullThrows( + context.sourceCode.getTokenByRangeStart(endQuasi.range[0]), + NullThrowsReasons.MissingToken('}', 'closing template literal'), + ); + + return context.sourceCode.commentsExistBetween(startToken, endToken); + } + return { TemplateLiteral(node: TSESTree.TemplateLiteral): void { if (node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression) { @@ -106,6 +124,10 @@ export default createRule<[], MessageId>({ isUnderlyingTypeString(node.expressions[0]); if (hasSingleStringVariable) { + if (hasCommentsBetweenQuasi(node.quasis[0], node.quasis[1])) { + return; + } + context.report({ loc: rangeToLoc(context.sourceCode, [ node.expressions[0].range[0] - 2, @@ -132,7 +154,7 @@ export default createRule<[], MessageId>({ nextQuasi: node.quasis[index + 1], prevQuasi: node.quasis[index], })) - .filter(({ expression, nextQuasi }) => { + .filter(({ expression, nextQuasi, prevQuasi }) => { if ( isUndefinedIdentifier(expression) || isInfinityIdentifier(expression) || @@ -141,6 +163,11 @@ export default createRule<[], MessageId>({ return true; } + // allow expressions that include comments + if (hasCommentsBetweenQuasi(prevQuasi, nextQuasi)) { + return false; + } + if (isLiteral(expression)) { // allow trailing whitespace literal if (startsWithNewLine(nextQuasi.value.raw)) { 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 d178ddd95f6a..6043b2b8b50d 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 @@ -310,10 +310,6 @@ const invalidCases: readonly InvalidTestCase< }\`; `, errors: [ - { - line: 2, - messageId: 'noUnnecessaryTemplateExpression', - }, { column: 2, endColumn: 2, @@ -331,12 +327,18 @@ const invalidCases: readonly InvalidTestCase< ], output: [ ` -\`use\${ - \`less\` -}\`; +\`u\${ + // hopefully this comment is not needed. + 'se' + +}le\${ \`ss\` }\`; `, ` -\`useless\`; +\`u\${ + // hopefully this comment is not needed. + 'se' + +}less\`; `, ], }, @@ -1104,6 +1106,42 @@ this code has trailing whitespace: \${' '} \`trailing position interpolated empty string also makes whitespace clear \${''} \`; `, + ` +\` +\${/* intentional comment before */ 'bar'} +...\`; + `, + ` +\` +\${'bar' /* intentional comment after */} +...\`; + `, + ` +\` +\${/* intentional comment before */ 'bar' /* intentional comment after */} +...\`; + `, + ` +\`\${/* intentional before */ 'bar'}\`; + `, + ` +\`\${'bar' /* intentional comment after */}\`; + `, + ` +\`\${/* intentional comment before */ 'bar' /* intentional comment after */}\`; + `, + ` +\`\${ + // intentional comment before + 'bar' +}\`; + `, + ` +\`\${ + 'bar' + // intentional comment after +}\`; + `, ], invalid: [