From d832a020f197b47518053d5b1fa07c59e9d54335 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Thu, 14 Dec 2023 11:11:43 +0200 Subject: [PATCH 1/5] feat(eslint-plugin): [no-useless-template-literals] add fix suggestions --- .../src/rules/no-useless-template-literals.ts | 60 +++- .../no-useless-template-literals.test.ts | 260 +++++++++++++++++- 2 files changed, 314 insertions(+), 6 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 48c714edb90a..db2692304828 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -1,4 +1,4 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; @@ -15,7 +15,8 @@ type MessageId = 'noUselessTemplateLiteral'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', meta: { - type: 'problem', + hasSuggestions: true, + type: 'suggestion', docs: { description: 'Disallow unnecessary template literals', recommended: 'strict', @@ -68,6 +69,27 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', + suggest: [ + { + 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, + ]), + ]; + }, + }, + ], }); return; @@ -83,6 +105,40 @@ export default createRule<[], MessageId>({ context.report({ node: expression, messageId: 'noUselessTemplateLiteral', + suggest: [ + { + 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, + ]), + ]; + + // Remove quotes for string literals (i.e. `'a'` will become `a`). + const isStringLiteral = + isUnderlyingTypeString(expression) && + expression.type === AST_NODE_TYPES.Literal; + + if (isStringLiteral) { + fixes.push(fixer.replaceText(expression, expression.value)); + } + + 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 index 54ac89c2c797..a19495ce4abe 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 @@ -143,20 +143,76 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 5, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`1`;', + }, + ], }, ], }, + { - code: '`${1n}`;', + code: noFormat`\`\${ 1 }\`;`, errors: [ { messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 6, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`1`;', + }, + ], + }, + ], + }, + + { + code: noFormat`\`\${ 'a' }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: `'a';`, + }, + ], + }, + ], + }, + + { + code: noFormat`\`\${ "a" }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: `"a";`, + }, + ], + }, + ], + }, + + { + code: noFormat`\`\${ 'a' + 'b' }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: `'a' + 'b';`, + }, + ], }, ], }, + { code: '`${true}`;', errors: [ @@ -165,9 +221,31 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 8, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`true`;', + }, + ], + }, + ], + }, + + { + code: noFormat`\`\${ true }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`true`;', + }, + ], }, ], }, + { code: '`${null}`;', errors: [ @@ -176,9 +254,31 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 8, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`null`;', + }, + ], }, ], }, + + { + code: noFormat`\`\${ null }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`null`;', + }, + ], + }, + ], + }, + { code: '`${undefined}`;', errors: [ @@ -187,9 +287,31 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 13, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`undefined`;', + }, + ], }, ], }, + + { + code: noFormat`\`\${ undefined }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`undefined`;', + }, + ], + }, + ], + }, + { code: "`${'a'}${'b'}`;", errors: [ @@ -198,12 +320,48 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 7, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: "`a${'b'}`;", + }, + ], }, { messageId: 'noUselessTemplateLiteral', line: 1, column: 10, endColumn: 13, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: "`${'a'}b`;", + }, + ], + }, + ], + }, + + { + code: noFormat`\`\${ 'a' }\${ 'b' }\`;`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: noFormat`\`a\${ 'b' }\`;`, + }, + ], + }, + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: noFormat`\`\${ 'a' }b\`;`, + }, + ], }, ], }, @@ -219,6 +377,15 @@ ruleTester.run('no-useless-template-literals', rule, { line: 3, column: 17, endColumn: 20, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: ` + declare const b: 'b'; + \`a\${b}c\`; + `, + }, + ], }, ], }, @@ -231,6 +398,12 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 5, endColumn: 8, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`ab`;', + }, + ], }, ], }, @@ -243,12 +416,24 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 14, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`1 + 1 = ${2}`;', + }, + ], }, { messageId: 'noUselessTemplateLiteral', line: 1, column: 17, endColumn: 18, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: "`${'1 + 1 = '}2`;", + }, + ], }, ], }, @@ -261,12 +446,24 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 7, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: '`a${true}`;', + }, + ], }, { messageId: 'noUselessTemplateLiteral', line: 1, column: 10, endColumn: 14, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: "`${'a'}true`;", + }, + ], }, ], }, @@ -282,6 +479,36 @@ ruleTester.run('no-useless-template-literals', rule, { line: 3, column: 12, endColumn: 18, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: ` + declare const string: 'a'; + string; + `, + }, + ], + }, + ], + }, + + { + code: noFormat` + declare const string: 'a'; + \`\${ string }\`; + `, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: ` + declare const string: 'a'; + string; + `, + }, + ], }, ], }, @@ -294,6 +521,12 @@ ruleTester.run('no-useless-template-literals', rule, { line: 1, column: 4, endColumn: 30, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: "String(Symbol.for('test'));", + }, + ], }, ], }, @@ -309,6 +542,15 @@ ruleTester.run('no-useless-template-literals', rule, { line: 3, column: 12, endColumn: 24, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: ` + declare const intersection: string & { _brand: 'test-brand' }; + intersection; + `, + }, + ], }, ], }, @@ -325,6 +567,16 @@ ruleTester.run('no-useless-template-literals', rule, { line: 3, column: 14, endColumn: 17, + suggestions: [ + { + messageId: 'noUselessTemplateLiteral', + output: ` + function func(arg: T) { + arg; + } + `, + }, + ], }, ], }, From 985a0624dddc349252894646cd5094349f86114a Mon Sep 17 00:00:00 2001 From: StyleShit Date: Thu, 14 Dec 2023 11:16:32 +0200 Subject: [PATCH 2/5] change message --- .../src/rules/no-useless-template-literals.ts | 9 ++-- .../no-useless-template-literals.test.ts | 52 +++++++++---------- 2 files changed, 32 insertions(+), 29 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 db2692304828..c09073126043 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -10,7 +10,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUselessTemplateLiteral'; +type MessageId = 'noUselessTemplateLiteral' | 'removeUselessTemplateLiteral'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', @@ -25,6 +25,9 @@ export default createRule<[], MessageId>({ messages: { noUselessTemplateLiteral: 'Template literal expression is unnecessary and can be simplified.', + + removeUselessTemplateLiteral: + 'Remove unnecessary template literal expression.', }, schema: [], }, @@ -71,7 +74,7 @@ export default createRule<[], MessageId>({ messageId: 'noUselessTemplateLiteral', suggest: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', fix(fixer): TSESLint.RuleFix[] { const [prevQuasi, nextQuasi] = node.quasis; @@ -107,7 +110,7 @@ export default createRule<[], MessageId>({ messageId: 'noUselessTemplateLiteral', suggest: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', fix(fixer): TSESLint.RuleFix[] { const index = node.expressions.indexOf(expression); const prevQuasi = node.quasis[index]; 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 a19495ce4abe..eb49fb999503 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 @@ -145,7 +145,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 5, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`1`;', }, ], @@ -160,7 +160,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`1`;', }, ], @@ -175,7 +175,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: `'a';`, }, ], @@ -190,7 +190,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: `"a";`, }, ], @@ -205,7 +205,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: `'a' + 'b';`, }, ], @@ -223,7 +223,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 8, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`true`;', }, ], @@ -238,7 +238,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`true`;', }, ], @@ -256,7 +256,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 8, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`null`;', }, ], @@ -271,7 +271,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`null`;', }, ], @@ -289,7 +289,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 13, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`undefined`;', }, ], @@ -304,7 +304,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`undefined`;', }, ], @@ -322,7 +322,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 7, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: "`a${'b'}`;", }, ], @@ -334,7 +334,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 13, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: "`${'a'}b`;", }, ], @@ -349,7 +349,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: noFormat`\`a\${ 'b' }\`;`, }, ], @@ -358,7 +358,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: noFormat`\`\${ 'a' }b\`;`, }, ], @@ -379,7 +379,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 20, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: ` declare const b: 'b'; \`a\${b}c\`; @@ -400,7 +400,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 8, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`ab`;', }, ], @@ -418,7 +418,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 14, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`1 + 1 = ${2}`;', }, ], @@ -430,7 +430,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 18, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: "`${'1 + 1 = '}2`;", }, ], @@ -448,7 +448,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 7, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: '`a${true}`;', }, ], @@ -460,7 +460,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 14, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: "`${'a'}true`;", }, ], @@ -481,7 +481,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 18, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: ` declare const string: 'a'; string; @@ -502,7 +502,7 @@ ruleTester.run('no-useless-template-literals', rule, { messageId: 'noUselessTemplateLiteral', suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: ` declare const string: 'a'; string; @@ -523,7 +523,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 30, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: "String(Symbol.for('test'));", }, ], @@ -544,7 +544,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 24, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: ` declare const intersection: string & { _brand: 'test-brand' }; intersection; @@ -569,7 +569,7 @@ ruleTester.run('no-useless-template-literals', rule, { endColumn: 17, suggestions: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'removeUselessTemplateLiteral', output: ` function func(arg: T) { arg; From d36ea2216e8c225a9c588f44a40b5f12b8063205 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Thu, 14 Dec 2023 11:18:50 +0200 Subject: [PATCH 3/5] add quasis to cspell --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index 080101b25f08..1bf064b538c6 100644 --- a/.cspell.json +++ b/.cspell.json @@ -106,6 +106,7 @@ "preact", "Premade", "prettier's", + "quasis", "Quickstart", "recurse", "redeclaration", From aa148cd968a23fa6a2b6af5016cfa17e4f22b4c5 Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sat, 16 Dec 2023 22:19:55 +0200 Subject: [PATCH 4/5] add some test cases --- .../src/rules/no-useless-template-literals.ts | 7 ++- .../no-useless-template-literals.test.ts | 60 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) 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 c09073126043..183c76a16900 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -135,7 +135,12 @@ export default createRule<[], MessageId>({ expression.type === AST_NODE_TYPES.Literal; if (isStringLiteral) { - fixes.push(fixer.replaceText(expression, expression.value)); + const escapedValue = expression.value.replace( + /([`$])/g, + '\\$1', + ); + + fixes.push(fixer.replaceText(expression, escapedValue)); } 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 index eb49fb999503..2c22b9b89f98 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 @@ -580,5 +580,65 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + + { + code: "`${'`'}`;", + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'removeUselessTemplateLiteral', + output: "'`';", + }, + ], + }, + ], + }, + + { + code: "`back${'`'}tick`;", + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'removeUselessTemplateLiteral', + output: '`back\\`tick`;', + }, + ], + }, + ], + }, + + { + code: "`dollar${'${`this is test`}'}sign`;", + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'removeUselessTemplateLiteral', + output: '`dollar\\${\\`this is test\\`}sign`;', + }, + ], + }, + ], + }, + + { + code: '`complex${\'`${"`${test}`"}`\'}case`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + suggestions: [ + { + messageId: 'removeUselessTemplateLiteral', + output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', + }, + ], + }, + ], + }, ], }); From 1d172274ba403058ad3eeb2afe913a3f27e6bdea Mon Sep 17 00:00:00 2001 From: StyleShit Date: Sun, 17 Dec 2023 19:08:51 +0200 Subject: [PATCH 5/5] use fix instead of suggestion --- .../src/rules/no-useless-template-literals.ts | 117 ++++---- .../no-useless-template-literals.test.ts | 276 +++++------------- 2 files changed, 123 insertions(+), 270 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 183c76a16900..d5a8a602b6b4 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -10,12 +10,12 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUselessTemplateLiteral' | 'removeUselessTemplateLiteral'; +type MessageId = 'noUselessTemplateLiteral'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', meta: { - hasSuggestions: true, + fixable: 'code', type: 'suggestion', docs: { description: 'Disallow unnecessary template literals', @@ -25,9 +25,6 @@ export default createRule<[], MessageId>({ messages: { noUselessTemplateLiteral: 'Template literal expression is unnecessary and can be simplified.', - - removeUselessTemplateLiteral: - 'Remove unnecessary template literal expression.', }, schema: [], }, @@ -72,27 +69,22 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', - suggest: [ - { - messageId: 'removeUselessTemplateLiteral', - 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 [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, + ]), + ]; + }, }); return; @@ -108,45 +100,40 @@ export default createRule<[], MessageId>({ context.report({ node: expression, messageId: 'noUselessTemplateLiteral', - suggest: [ - { - messageId: 'removeUselessTemplateLiteral', - 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, - ]), - ]; - - // Remove quotes for string literals (i.e. `'a'` will become `a`). - const isStringLiteral = - isUnderlyingTypeString(expression) && - expression.type === AST_NODE_TYPES.Literal; - - if (isStringLiteral) { - const escapedValue = expression.value.replace( - /([`$])/g, - '\\$1', - ); - - fixes.push(fixer.replaceText(expression, escapedValue)); - } - - return fixes; - }, - }, - ], + 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, + ]), + ]; + + // Remove quotes for string literals (i.e. `'a'` will become `a`). + const isStringLiteral = + isUnderlyingTypeString(expression) && + expression.type === AST_NODE_TYPES.Literal; + + if (isStringLiteral) { + const escapedValue = expression.value.replace( + /([`$\\])/g, + '\\$1', + ); + + fixes.push(fixer.replaceText(expression, escapedValue)); + } + + 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 index 2c22b9b89f98..674b4ccefe77 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 @@ -137,231 +137,154 @@ ruleTester.run('no-useless-template-literals', rule, { invalid: [ { code: '`${1}`;', + output: '`1`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 5, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`1`;', - }, - ], }, ], }, { code: noFormat`\`\${ 1 }\`;`, + output: '`1`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`1`;', - }, - ], }, ], }, { code: noFormat`\`\${ 'a' }\`;`, + output: `'a';`, errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: `'a';`, - }, - ], }, ], }, { code: noFormat`\`\${ "a" }\`;`, + output: `"a";`, errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: `"a";`, - }, - ], }, ], }, { code: noFormat`\`\${ 'a' + 'b' }\`;`, + output: `'a' + 'b';`, errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: `'a' + 'b';`, - }, - ], }, ], }, { code: '`${true}`;', + output: '`true`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 8, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`true`;', - }, - ], }, ], }, { code: noFormat`\`\${ true }\`;`, + output: '`true`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`true`;', - }, - ], }, ], }, { code: '`${null}`;', + output: '`null`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 8, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`null`;', - }, - ], }, ], }, { code: noFormat`\`\${ null }\`;`, + output: '`null`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`null`;', - }, - ], }, ], }, { code: '`${undefined}`;', + output: '`undefined`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 13, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`undefined`;', - }, - ], }, ], }, { code: noFormat`\`\${ undefined }\`;`, + output: '`undefined`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`undefined`;', - }, - ], }, ], }, { - code: "`${'a'}${'b'}`;", + code: "`${'a'} ${'b'}`;", + output: '`a b`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 7, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "`a${'b'}`;", - }, - ], }, { messageId: 'noUselessTemplateLiteral', line: 1, - column: 10, - endColumn: 13, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "`${'a'}b`;", - }, - ], + column: 11, + endColumn: 14, }, ], }, { - code: noFormat`\`\${ 'a' }\${ 'b' }\`;`, + code: noFormat`\`\${ 'a' } \${ 'b' }\`;`, + output: '`a b`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: noFormat`\`a\${ 'b' }\`;`, - }, - ], }, { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: noFormat`\`\${ 'a' }b\`;`, - }, - ], }, ], }, @@ -371,99 +294,67 @@ ruleTester.run('no-useless-template-literals', rule, { declare const b: 'b'; \`a\${b}\${'c'}\`; `, + output: ` + declare const b: 'b'; + \`a\${b}c\`; + `, errors: [ { messageId: 'noUselessTemplateLiteral', line: 3, column: 17, endColumn: 20, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: ` - declare const b: 'b'; - \`a\${b}c\`; - `, - }, - ], }, ], }, { code: "`a${'b'}`;", + output: '`ab`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 5, endColumn: 8, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`ab`;', - }, - ], }, ], }, { - code: "`${'1 + 1 = '}${2}`;", + code: "`${'1 + 1 ='} ${2}`;", + output: '`1 + 1 = 2`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, - endColumn: 14, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`1 + 1 = ${2}`;', - }, - ], + endColumn: 13, }, { messageId: 'noUselessTemplateLiteral', line: 1, column: 17, endColumn: 18, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "`${'1 + 1 = '}2`;", - }, - ], }, ], }, { - code: "`${'a'}${true}`;", + code: "`${'a'} ${true}`;", + output: '`a true`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, endColumn: 7, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`a${true}`;', - }, - ], }, { messageId: 'noUselessTemplateLiteral', line: 1, - column: 10, - endColumn: 14, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "`${'a'}true`;", - }, - ], + column: 11, + endColumn: 15, }, ], }, @@ -473,21 +364,16 @@ ruleTester.run('no-useless-template-literals', rule, { declare const string: 'a'; \`\${string}\`; `, + output: ` + declare const string: 'a'; + string; + `, errors: [ { messageId: 'noUselessTemplateLiteral', line: 3, column: 12, endColumn: 18, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: ` - declare const string: 'a'; - string; - `, - }, - ], }, ], }, @@ -497,36 +383,26 @@ ruleTester.run('no-useless-template-literals', rule, { declare const string: 'a'; \`\${ string }\`; `, - errors: [ - { - messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: ` + 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, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "String(Symbol.for('test'));", - }, - ], }, ], }, @@ -536,21 +412,16 @@ ruleTester.run('no-useless-template-literals', rule, { 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, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: ` - declare const intersection: string & { _brand: 'test-brand' }; - intersection; - `, - }, - ], }, ], }, @@ -561,82 +432,77 @@ ruleTester.run('no-useless-template-literals', rule, { \`\${arg}\`; } `, + output: ` + function func(arg: T) { + arg; + } + `, errors: [ { messageId: 'noUselessTemplateLiteral', line: 3, column: 14, endColumn: 17, - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: ` - function func(arg: T) { - arg; - } - `, - }, - ], }, ], }, { code: "`${'`'}`;", + output: "'`';", errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: "'`';", - }, - ], }, ], }, { code: "`back${'`'}tick`;", + output: '`back\\`tick`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`back\\`tick`;', - }, - ], }, ], }, { code: "`dollar${'${`this is test`}'}sign`;", + output: '`dollar\\${\\`this is test\\`}sign`;', errors: [ { messageId: 'noUselessTemplateLiteral', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`dollar\\${\\`this is test\\`}sign`;', - }, - ], }, ], }, { 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', - suggestions: [ - { - messageId: 'removeUselessTemplateLiteral', - output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', - }, - ], }, ], },