diff --git a/packages/eslint-plugin/docs/rules/no-throw-literal.mdx b/packages/eslint-plugin/docs/rules/no-throw-literal.mdx index d5919b3bdc63..9bd9481ffa15 100644 --- a/packages/eslint-plugin/docs/rules/no-throw-literal.mdx +++ b/packages/eslint-plugin/docs/rules/no-throw-literal.mdx @@ -16,10 +16,10 @@ This rule restricts what can be thrown as an exception. :::warning This rule is being renamed to [`only-throw-error`](./only-throw-error.mdx). -When it was first created, it only prevented literals from being thrown (hence the name), but it has now been expanded to only allow expressions which have a possibility of being an `Error` object. -With the `allowThrowingAny` and `allowThrowingUnknown` options, it can be configured to only allow throwing values which are guaranteed to be an instance of `Error`. +The current name, `no-throw-literal`, will be removed in a future major version of typescript-eslint. -The current name `no-throw-literal` will be removed in a future major version of typescript-eslint. +When it was first created, this rule only prevented literals from being thrown (hence the name), but it has now been expanded to only allow expressions which have a possibility of being an `Error` object. +With the `allowThrowingAny` and `allowThrowingUnknown` options, it can be configured to only allow throwing values which are guaranteed to be an instance of `Error`. ::: {/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/no-useless-template-expression.mdx b/packages/eslint-plugin/docs/rules/no-useless-template-expression.mdx new file mode 100644 index 000000000000..2b6a28802b22 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-useless-template-expression.mdx @@ -0,0 +1,87 @@ +--- +description: 'Disallow unnecessary template expressions.' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-useless-template-expression** for documentation. + +This rule reports template literals that contain substitution expressions (also variously referred to as embedded expressions or string interpolations) that are unnecessary and can be simplified. + +:::info[Migration from `no-useless-template-literals`] + +This rule was formerly known as [`no-useless-template-literals`](./no-useless-template-literals.mdx). +We encourage users to migrate to the new name, `no-useless-template-expression`, as the old name will be removed in a future major version of typescript-eslint. + +The new name is a drop-in replacement with identical functionality. + +::: + +## Examples + + + + +```ts +// Static values can be incorporated into the surrounding template. + +const ab1 = `${'a'}${'b'}`; +const ab2 = `a${'b'}`; + +const stringWithNumber = `${'1 + 1 = '}${2}`; + +const stringWithBoolean = `${'true is '}${true}`; + +// Some simple expressions that are already strings +// can be rewritten without a template at all. + +const text = 'a'; +const wrappedText = `${text}`; + +declare const intersectionWithString: string & { _brand: 'test-brand' }; +const wrappedIntersection = `${intersectionWithString}`; +``` + + + + +```ts +// Static values can be incorporated into the surrounding template. + +const ab1 = `ab`; +const ab2 = `ab`; + +const stringWithNumber = `1 + 1 = 2`; + +const stringWithBoolean = `true is true`; + +// Some simple expressions that are already strings +// can be rewritten without a template at all. + +const text = 'a'; +const wrappedText = text; + +declare const intersectionWithString: string & { _brand: 'test-brand' }; +const wrappedIntersection = intersectionWithString; +``` + + + + +:::info +This rule does not aim to flag template literals without substitution expressions that could have been written as an ordinary string. +That is to say, this rule will not help you turn `` `this` `` into `"this"`. +If you are looking for such a rule, you can configure the [`@stylistic/ts/quotes`](https://eslint.style/rules/ts/quotes) rule to do this. +::: + +## When Not To Use It + +When you want to allow string expressions inside template literals. + +## Related To + +- [`restrict-template-expressions`](./restrict-template-expressions.mdx) +- [`@stylistic/ts/quotes`](https://eslint.style/rules/ts/quotes) diff --git a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx index b229a4b1ff67..f02ec5ada9ec 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx +++ b/packages/eslint-plugin/docs/rules/no-useless-template-literals.mdx @@ -1,5 +1,5 @@ --- -description: 'Disallow unnecessary template literals.' +description: 'Disallow unnecessary template expressions.' --- import Tabs from '@theme/Tabs'; @@ -9,53 +9,15 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-useless-template-literals** for documentation. -This rule reports template literals that can be simplified to a normal string literal. +This rule reports template literals that contain substitution expressions (also variously referred to as embedded expressions or string interpolations) that are unnecessary and can be simplified. -## Examples +:::warning +This rule is being renamed to [`no-useless-template-expression`](./no-useless-template-expression.mdx). +The current name, `no-useless-template-literals`, will be removed in a future major version of typescript-eslint. - - +After the creation of this rule, it was realized that the name `no-useless-template-literals` could be misleading, seeing as this rule only targets template literals with substitution expressions. +In particular, it does _not_ aim to flag useless template literals that look like `` `this` `` and could be simplified to `"this"`. +If you are looking for such a rule, you can configure the [`@stylistic/ts/quotes`](https://eslint.style/rules/ts/quotes) rule to do this. +::: -```ts -const ab1 = `${'a'}${'b'}`; -const ab2 = `a${'b'}`; - -const stringWithNumber = `${'1 + 1 = '}${2}`; - -const stringWithBoolean = `${'true is '}${true}`; - -const text = 'a'; -const wrappedText = `${text}`; - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = `${intersectionWithString}`; -``` - - - - -```ts -const ab1 = 'ab'; -const ab2 = 'ab'; - -const stringWithNumber = `1 + 1 = 2`; - -const stringWithBoolean = `true is true`; - -const text = 'a'; -const wrappedText = text; - -declare const intersectionWithString: string & { _brand: 'test-brand' }; -const wrappedIntersection = intersectionWithString; -``` - - - - -## When Not To Use It - -When you want to allow string expressions inside template literals. - -## Related To - -- [`restrict-template-expressions`](./restrict-template-expressions.mdx) +{/* Intentionally Omitted: When Not To Use It */} diff --git a/packages/eslint-plugin/docs/rules/only-throw-error.mdx b/packages/eslint-plugin/docs/rules/only-throw-error.mdx index cd27f8cc4387..0070d648a888 100644 --- a/packages/eslint-plugin/docs/rules/only-throw-error.mdx +++ b/packages/eslint-plugin/docs/rules/only-throw-error.mdx @@ -14,6 +14,15 @@ The fundamental benefit of `Error` objects is that they automatically keep track This rule restricts what can be thrown as an exception. +:::info[Migration from `no-throw-literal`] + +This rule was formerly known as [`no-throw-literal`](./no-throw-literal.mdx). +We encourage users to migrate to the new name, `only-throw-error`, as the old name will be removed in a future major version of typescript-eslint. + +The new name is a drop-in replacement with identical functionality. + +::: + ## Examples This rule is aimed at maintaining consistency when throwing exception by disallowing to throw literals and other expressions which cannot possibly be an `Error` object. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 1e7304e45a36..ce40957c831d 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -113,7 +113,7 @@ export = { 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-empty-export': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'no-throw-literal': 'off', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index fd3a8c2d7cbd..6400ad4f43ee 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -39,6 +39,7 @@ export = { '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', + '@typescript-eslint/no-useless-template-expression': 'off', '@typescript-eslint/no-useless-template-literals': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts index 12709933dfb7..0815a6007e31 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked-only.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked-only.ts @@ -33,7 +33,7 @@ export = { '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-includes': 'error', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 26d8d9698812..2396c00ced5e 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -60,7 +60,7 @@ export = { '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', '@typescript-eslint/no-var-requires': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 64c6a872ed69..9befde4df7bf 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -97,6 +97,7 @@ import noUnusedVars from './no-unused-vars'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; import noUselessEmptyExport from './no-useless-empty-export'; +import noUselessTemplateExpression from './no-useless-template-expression'; import noUselessTemplateLiterals from './no-useless-template-literals'; import noVarRequires from './no-var-requires'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; @@ -242,6 +243,7 @@ export default { 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, 'no-useless-empty-export': noUselessEmptyExport, + 'no-useless-template-expression': noUselessTemplateExpression, 'no-useless-template-literals': noUselessTemplateLiterals, 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, diff --git a/packages/eslint-plugin/src/rules/no-useless-template-expression.ts b/packages/eslint-plugin/src/rules/no-useless-template-expression.ts new file mode 100644 index 000000000000..e7afc261e2fe --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-useless-template-expression.ts @@ -0,0 +1,175 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as ts from 'typescript'; + +import { + createRule, + getConstrainedTypeAtLocation, + getParserServices, + getStaticStringValue, + isTypeFlagSet, + isUndefinedIdentifier, +} from '../util'; + +type MessageId = 'noUselessTemplateExpression'; + +export default createRule<[], MessageId>({ + name: 'no-useless-template-expression', + meta: { + fixable: 'code', + type: 'suggestion', + docs: { + description: 'Disallow unnecessary template expressions', + recommended: 'strict', + requiresTypeChecking: true, + }, + messages: { + noUselessTemplateExpression: + '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: 'noUselessTemplateExpression', + 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; + } + + const fixableExpressions = node.expressions.filter( + expression => + isLiteral(expression) || + isTemplateLiteral(expression) || + isUndefinedIdentifier(expression) || + isInfinityIdentifier(expression) || + isNaNIdentifier(expression), + ); + + fixableExpressions.forEach(expression => { + context.report({ + node: expression, + messageId: 'noUselessTemplateExpression', + 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/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 4610d406465a..9156c6e4053e 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -11,7 +11,7 @@ import { isUndefinedIdentifier, } from '../util'; -type MessageId = 'noUselessTemplateLiteral'; +type MessageId = 'noUselessTemplateExpression'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', @@ -19,15 +19,16 @@ export default createRule<[], MessageId>({ fixable: 'code', type: 'suggestion', docs: { - description: 'Disallow unnecessary template literals', - recommended: 'strict', + description: 'Disallow unnecessary template expressions', requiresTypeChecking: true, }, messages: { - noUselessTemplateLiteral: + noUselessTemplateExpression: 'Template literal expression is unnecessary and can be simplified.', }, schema: [], + deprecated: true, + replacedBy: ['@typescript-eslint/no-useless-template-expression'], }, defaultOptions: [], create(context) { @@ -91,7 +92,7 @@ export default createRule<[], MessageId>({ if (hasSingleStringVariable) { context.report({ node: node.expressions[0], - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', fix(fixer): TSESLint.RuleFix[] { const [prevQuasi, nextQuasi] = node.quasis; @@ -125,7 +126,7 @@ export default createRule<[], MessageId>({ fixableExpressions.forEach(expression => { context.report({ node: expression, - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', fix(fixer): TSESLint.RuleFix[] { const index = node.expressions.indexOf(expression); const prevQuasi = node.quasis[index]; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-expression.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-expression.shot new file mode 100644 index 000000000000..d5813404d862 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-useless-template-expression.shot @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-useless-template-expression.mdx code examples ESLint output 1`] = ` +"Incorrect + +// Static values can be incorporated into the surrounding template. + +const ab1 = \`\${'a'}\${'b'}\`; + ~~~ Template literal expression is unnecessary and can be simplified. + ~~~ Template literal expression is unnecessary and can be simplified. +const ab2 = \`a\${'b'}\`; + ~~~ Template literal expression is unnecessary and can be simplified. + +const stringWithNumber = \`\${'1 + 1 = '}\${2}\`; + ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. + ~ Template literal expression is unnecessary and can be simplified. + +const stringWithBoolean = \`\${'true is '}\${true}\`; + ~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. + ~~~~ Template literal expression is unnecessary and can be simplified. + +// Some simple expressions that are already strings +// can be rewritten without a template at all. + +const text = 'a'; +const wrappedText = \`\${text}\`; + ~~~~ Template literal expression is unnecessary and can be simplified. + +declare const intersectionWithString: string & { _brand: 'test-brand' }; +const wrappedIntersection = \`\${intersectionWithString}\`; + ~~~~~~~~~~~~~~~~~~~~~~ Template literal expression is unnecessary and can be simplified. +" +`; + +exports[`Validating rule docs no-useless-template-expression.mdx code examples ESLint output 2`] = ` +"Correct + +// Static values can be incorporated into the surrounding template. + +const ab1 = \`ab\`; +const ab2 = \`ab\`; + +const stringWithNumber = \`1 + 1 = 2\`; + +const stringWithBoolean = \`true is true\`; + +// Some simple expressions that are already strings +// can be rewritten without a template at all. + +const text = 'a'; +const wrappedText = text; + +declare const intersectionWithString: string & { _brand: 'test-brand' }; +const wrappedIntersection = intersectionWithString; +" +`; diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-expression.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-expression.test.ts new file mode 100644 index 000000000000..628057ac7d17 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-useless-template-expression.test.ts @@ -0,0 +1,641 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-useless-template-expression'; +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-expression', 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: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 5, + }, + ], + }, + { + code: '`${1n}`;', + output: '`1`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 6, + }, + ], + }, + { + code: '`${/a/}`;', + output: '`/a/`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 7, + }, + ], + }, + + { + code: noFormat`\`\${ 1 }\`;`, + output: '`1`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: noFormat`\`\${ 'a' }\`;`, + output: `'a';`, + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: noFormat`\`\${ "a" }\`;`, + output: `"a";`, + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: noFormat`\`\${ 'a' + 'b' }\`;`, + output: `'a' + 'b';`, + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: '`${true}`;', + output: '`true`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 8, + }, + ], + }, + + { + code: noFormat`\`\${ true }\`;`, + output: '`true`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: '`${null}`;', + output: '`null`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 8, + }, + ], + }, + + { + code: noFormat`\`\${ null }\`;`, + output: '`null`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: '`${undefined}`;', + output: '`undefined`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 13, + }, + ], + }, + + { + code: noFormat`\`\${ undefined }\`;`, + output: '`undefined`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: '`${Infinity}`;', + output: '`Infinity`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 12, + }, + ], + }, + + { + code: '`${NaN}`;', + output: '`NaN`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 7, + }, + ], + }, + + { + code: "`${'a'} ${'b'}`;", + output: '`a b`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 7, + }, + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 11, + endColumn: 14, + }, + ], + }, + + { + code: noFormat`\`\${ 'a' } \${ 'b' }\`;`, + output: '`a b`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: ` + declare const b: 'b'; + \`a\${b}\${'c'}\`; + `, + output: ` + declare const b: 'b'; + \`a\${b}c\`; + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 3, + column: 17, + endColumn: 20, + }, + ], + }, + + { + code: "`use${'less'}`;", + output: '`useless`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + }, + ], + }, + + { + code: '`use${`less`}`;', + output: '`useless`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + 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: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: noFormat` +\`u\${ + // hopefully this comment is not needed. + 'se' + +}\${ + \`le\${ \`ss\` }\` +}\`; + `, + output: ` +\`use\${ + \`less\` +}\`; + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 4, + }, + { + messageId: 'noUselessTemplateExpression', + line: 7, + column: 3, + endLine: 7, + }, + { + messageId: 'noUselessTemplateExpression', + line: 7, + column: 10, + endLine: 7, + }, + ], + }, + { + code: noFormat` +\`use\${ + \`less\` +}\`; + `, + output: ` +\`useless\`; + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 3, + column: 3, + endColumn: 9, + }, + ], + }, + + { + code: "`${'1 + 1 ='} ${2}`;", + output: '`1 + 1 = 2`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 13, + }, + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 17, + endColumn: 18, + }, + ], + }, + + { + code: "`${'a'} ${true}`;", + output: '`a true`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 4, + endColumn: 7, + }, + { + messageId: 'noUselessTemplateExpression', + line: 1, + column: 11, + endColumn: 15, + }, + ], + }, + + { + code: ` + declare const string: 'a'; + \`\${string}\`; + `, + output: ` + declare const string: 'a'; + string; + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 3, + column: 12, + endColumn: 18, + }, + ], + }, + + { + code: noFormat` + declare const string: 'a'; + \`\${ string }\`; + `, + output: ` + declare const string: 'a'; + string; + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: "`${String(Symbol.for('test'))}`;", + output: "String(Symbol.for('test'));", + errors: [ + { + messageId: 'noUselessTemplateExpression', + 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: 'noUselessTemplateExpression', + line: 3, + column: 12, + endColumn: 24, + }, + ], + }, + + { + code: ` + function func(arg: T) { + \`\${arg}\`; + } + `, + output: ` + function func(arg: T) { + arg; + } + `, + errors: [ + { + messageId: 'noUselessTemplateExpression', + line: 3, + column: 14, + endColumn: 17, + }, + ], + }, + + { + code: "`${'`'}`;", + output: "'`';", + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: "`back${'`'}tick`;", + output: '`back\\`tick`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: "`dollar${'${`this is test`}'}sign`;", + output: '`dollar\\${\\`this is test\\`}sign`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: '`complex${\'`${"`${test}`"}`\'}case`;', + output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: "`some ${'\\\\${test}'} string`;", + output: '`some \\\\\\${test} string`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + + { + code: "`some ${'\\\\`'} string`;", + output: '`some \\\\\\` string`;', + errors: [ + { + messageId: 'noUselessTemplateExpression', + }, + ], + }, + ], +}); 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..06eacb1e31e6 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,7 +143,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`1`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 5, @@ -155,7 +155,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`1`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 6, @@ -167,7 +167,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`/a/`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 7, @@ -180,7 +180,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`1`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -190,7 +190,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: `'a';`, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -200,7 +200,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: `"a";`, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -210,7 +210,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: `'a' + 'b';`, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -220,7 +220,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`true`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 8, @@ -233,7 +233,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`true`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -243,7 +243,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`null`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 8, @@ -256,7 +256,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`null`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -266,7 +266,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`undefined`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 13, @@ -279,7 +279,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`undefined`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -289,7 +289,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`Infinity`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 12, @@ -302,7 +302,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`NaN`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 7, @@ -315,13 +315,13 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`a b`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 7, }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 11, endColumn: 14, @@ -334,10 +334,10 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`a b`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -353,7 +353,7 @@ ruleTester.run('no-useless-template-literals', rule, { `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 3, column: 17, endColumn: 20, @@ -366,7 +366,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`useless`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, }, ], @@ -377,7 +377,7 @@ ruleTester.run('no-useless-template-literals', rule, { output: '`useless`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, }, ], @@ -394,7 +394,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -416,17 +416,17 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 4, }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 7, column: 3, endLine: 7, }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 7, column: 10, endLine: 7, @@ -444,7 +444,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 3, column: 3, endColumn: 9, @@ -457,13 +457,13 @@ declare const nested: string, interpolation: string; output: '`1 + 1 = 2`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 13, }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 17, endColumn: 18, @@ -476,13 +476,13 @@ declare const nested: string, interpolation: string; output: '`a true`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 7, }, { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 11, endColumn: 15, @@ -501,7 +501,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 3, column: 12, endColumn: 18, @@ -520,7 +520,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -530,7 +530,7 @@ declare const nested: string, interpolation: string; output: "String(Symbol.for('test'));", errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 1, column: 4, endColumn: 30, @@ -549,7 +549,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 3, column: 12, endColumn: 24, @@ -570,7 +570,7 @@ declare const nested: string, interpolation: string; `, errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', line: 3, column: 14, endColumn: 17, @@ -583,7 +583,7 @@ declare const nested: string, interpolation: string; output: "'`';", errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -593,7 +593,7 @@ declare const nested: string, interpolation: string; output: '`back\\`tick`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -603,7 +603,7 @@ declare const nested: string, interpolation: string; output: '`dollar\\${\\`this is test\\`}sign`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -613,7 +613,7 @@ declare const nested: string, interpolation: string; output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -623,7 +623,7 @@ declare const nested: string, interpolation: string; output: '`some \\\\\\${test} string`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, @@ -633,7 +633,7 @@ declare const nested: string, interpolation: string; output: '`some \\\\\\` string`;', errors: [ { - messageId: 'noUselessTemplateLiteral', + messageId: 'noUselessTemplateExpression', }, ], }, diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-expression.shot b/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-expression.shot new file mode 100644 index 000000000000..f34596f9a1a4 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-useless-template-expression.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-useless-template-expression 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index efe0d16fceb8..71d701e3d1ca 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -122,7 +122,7 @@ export default ( 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-useless-empty-export': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', '@typescript-eslint/no-var-requires': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', 'no-throw-literal': 'off', diff --git a/packages/typescript-eslint/src/configs/disable-type-checked.ts b/packages/typescript-eslint/src/configs/disable-type-checked.ts index 8d2f29220aba..9be9ab93dfd3 100644 --- a/packages/typescript-eslint/src/configs/disable-type-checked.ts +++ b/packages/typescript-eslint/src/configs/disable-type-checked.ts @@ -42,6 +42,7 @@ export default ( '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-return': 'off', '@typescript-eslint/no-unsafe-unary-minus': 'off', + '@typescript-eslint/no-useless-template-expression': 'off', '@typescript-eslint/no-useless-template-literals': 'off', '@typescript-eslint/non-nullable-type-assertion-style': 'off', '@typescript-eslint/only-throw-error': 'off', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts index f17b5280ca49..a2dbdaab1059 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked-only.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked-only.ts @@ -42,7 +42,7 @@ export default ( '@typescript-eslint/no-unsafe-enum-comparison': 'error', '@typescript-eslint/no-unsafe-member-access': 'error', '@typescript-eslint/no-unsafe-return': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', '@typescript-eslint/prefer-includes': 'error', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index ad62ee749e25..20db0da63d1b 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -69,7 +69,7 @@ export default ( '@typescript-eslint/no-unused-vars': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', - '@typescript-eslint/no-useless-template-literals': 'error', + '@typescript-eslint/no-useless-template-expression': 'error', '@typescript-eslint/no-var-requires': 'error', 'no-throw-literal': 'off', '@typescript-eslint/only-throw-error': 'error', 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