diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx index 9262d9fea9be..aded1f4b85a0 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-template-expression.mdx @@ -66,6 +66,7 @@ enum ABC { C = 'C', } type ABCUnion = `${ABC}`; +type A = `${ABC.A}`; // Interpolating type parameters is allowed. type TextUtil = `${T}`; 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 842f549eb34a..6c70e134043e 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-template-expression.ts @@ -1,6 +1,7 @@ import type { TSESLint } from '@typescript-eslint/utils'; import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; import { @@ -76,16 +77,13 @@ export default createRule<[], MessageId>({ return isStringLike(type); } - /** - * Checks for whole enum types, i.e. `MyEnum`, and not their values, i.e. `MyEnum.A` - */ - function isEnumType(type: ts.Type): boolean { - const symbol = type.getSymbol(); - - return !!( - symbol?.valueDeclaration && - ts.isEnumDeclaration(symbol.valueDeclaration) - ); + function isEnumMemberType(type: ts.Type): boolean { + return tsutils.typeParts(type).some(t => { + const symbol = t.getSymbol(); + return !!( + symbol?.valueDeclaration && ts.isEnumMember(symbol.valueDeclaration) + ); + }); } const isLiteral = isNodeOfType(TSESTree.AST_NODE_TYPES.Literal); @@ -460,7 +458,7 @@ export default createRule<[], MessageId>({ constraintType && !isTypeParameter && isUnderlyingTypeString(constraintType) && - !isEnumType(constraintType) + !isEnumMemberType(constraintType) ) { reportSingleInterpolation(node); return; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot index 2d5241b52e5e..b4662d8bdcfd 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unnecessary-template-expression.shot @@ -59,6 +59,7 @@ enum ABC { C = 'C', } type ABCUnion = \`\${ABC}\`; +type A = \`\${ABC.A}\`; // Interpolating type parameters is allowed. type TextUtil = \`\${T}\`; 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 f60f87126f78..191cfeeb0de7 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 @@ -1188,6 +1188,60 @@ enum Foo { A = 1, B = 2, } +type Bar = \`\${Foo.A}\`; + `, + ` +enum Enum1 { + A = 'A1', + B = 'B1', +} + +enum Enum2 { + A = 'A2', + B = 'B2', +} + +type Union = \`\${Enum1 | Enum2}\`; + `, + ` +enum Enum1 { + A = 'A1', + B = 'B1', +} + +enum Enum2 { + A = 'A2', + B = 'B2', +} + +type Union = \`\${Enum1.A | Enum2.B}\`; + `, + ` +enum Enum1 { + A = 'A1', + B = 'B1', +} + +enum Enum2 { + A = 'A2', + B = 'B2', +} +type Enums = Enum1 | Enum2; +type Union = \`\${Enums}\`; + `, + ` +enum Enum { + A = 'A', + B = 'A', +} + +type Intersection = \`\${Enum1.A & string}\`; + `, + ` +enum Foo { + A = 'A', + B = 'B', +} type Bar = \`\${Foo.A}\`; `, ` @@ -1412,30 +1466,5 @@ type Bar = Foo; ], output: "type FooBar = 'foo' | 'bar';", }, - { - code: ` -enum Foo { - A = 'A', - B = 'B', -} -type Bar = \`\${Foo.A}\`; - `, - errors: [ - { - column: 13, - endColumn: 21, - endLine: 6, - line: 6, - messageId: 'noUnnecessaryTemplateExpression', - }, - ], - output: ` -enum Foo { - A = 'A', - B = 'B', -} -type Bar = Foo.A; - `, - }, ], });