Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions packages/eslint-plugin/src/rules/restrict-template-expressions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { TSESTree } from '@typescript-eslint/utils';
import type { Type, TypeChecker } from 'typescript';
import type { InterfaceType, Type, TypeChecker } from 'typescript';

import {
typeMatchesSomeSpecifier,
typeOrValueSpecifiersSchema,
} from '@typescript-eslint/type-utils';
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
import { TypeFlags } from 'typescript';
import { isObjectFlagSet, isObjectType } from 'ts-api-utils';
import { ObjectFlags, TypeFlags } from 'typescript';

import type { TypeOrValueSpecifier } from '../util';

Expand Down Expand Up @@ -130,6 +131,33 @@ export default createRule<Options, MessageId>({
({ option }) => options[option],
);

function hasBaseTypes(type: Type): type is InterfaceType {
return (
isObjectType(type) &&
isObjectFlagSet(type, ObjectFlags.Interface | ObjectFlags.Class)
);
}

function isAllowedTypeOrBase(type: Type, seen = new Set<Type>()): boolean {
if (seen.has(type)) {
return false;
}

seen.add(type);

if (typeMatchesSomeSpecifier(type, allow, program)) {
return true;
}

if (hasBaseTypes(type)) {
return checker
.getBaseTypes(type)
.some(base => isAllowedTypeOrBase(base, seen));
}

return false;
}

return {
TemplateLiteral(node: TSESTree.TemplateLiteral): void {
// don't check tagged template literals
Expand Down Expand Up @@ -165,7 +193,7 @@ export default createRule<Options, MessageId>({

return (
isTypeFlagSet(innerType, TypeFlags.StringLike) ||
typeMatchesSomeSpecifier(innerType, allow, program) ||
isAllowedTypeOrBase(innerType) ||
enabledOptionTesters.some(({ tester }) =>
tester(innerType, checker, recursivelyCheckType),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,52 @@ ruleTester.run('restrict-template-expressions', rule, {
'const msg = `arg = ${undefined}`;',
'const msg = `arg = ${123}`;',
"const msg = `arg = ${'abc'}`;",
// https://github.com/typescript-eslint/typescript-eslint/issues/11759
// allow should check base types
{
code: `
class Base {}
class Derived extends Base {}
const foo = new Base();
const bar = new Derived();
\`\${foo}\${bar}\`;
`,
options: [{ allow: [{ from: 'file', name: 'Base' }] }],
},
// allow should check base types - multi-level inheritance
{
code: `
class Base {}
class Derived extends Base {}
class DerivedTwice extends Derived {}
const value = new DerivedTwice();
\`\${value}\`;
`,
options: [{ allow: [{ from: 'file', name: 'Base' }] }],
},
// allow should check base types - interface inheritance
{
code: `
interface Base {
value: string;
}
interface Derived extends Base {
extra: number;
}
declare const obj: Derived;
\`\${obj}\`;
`,
options: [{ allow: [{ from: 'file', name: 'Base' }] }],
},
// allow list with type alias without base types
{
code: `
type Custom = { value: string };
declare const obj: Custom;
\`\${obj}\`;
`,
options: [{ allow: [{ from: 'file', name: 'Custom' }] }],
},
],

invalid: [
Expand Down Expand Up @@ -614,5 +660,42 @@ ruleTester.run('restrict-template-expressions', rule, {
],
options: [{ allowAny: true }],
},
// https://github.com/typescript-eslint/typescript-eslint/issues/11759
// derived type should error when base type is not in allow list
{
code: `
class Base {}
class Derived extends Base {}
const bar = new Derived();
\`\${bar}\`;
`,
errors: [
{
data: { type: 'Derived' },
messageId: 'invalidType',
},
],
options: [{ allow: [] }],
},
// derived interface should error when base type is not in allow list
{
code: `
interface Base {
value: string;
}
interface Derived extends Base {
extra: number;
}
declare const obj: Derived;
\`\${obj}\`;
`,
errors: [
{
data: { type: 'Derived' },
messageId: 'invalidType',
},
],
options: [{ allow: [] }],
},
],
});