Skip to content

Commit 685e530

Browse files
ntdiarybradzacher
andauthored
fix(typescript-estree): add validation to interface extends (#11271)
Co-authored-by: Brad Zacher <[email protected]>
1 parent d159a21 commit 685e530

File tree

7 files changed

+45
-25
lines changed

7 files changed

+45
-25
lines changed

packages/ast-spec/src/declaration/TSInterfaceDeclaration/fixtures/_error_/non-identifier-extends/snapshots/1-TSESTree-Error.shot

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ast-spec/src/declaration/TSInterfaceDeclaration/fixtures/_error_/non-identifier-extends/snapshots/3-Alignment-Error.shot

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ast-spec/src/legacy-fixtures/errorRecovery/fixtures/_error_/interface-multiple-extends/snapshots/1-TSESTree-Error.shot

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ast-spec/src/legacy-fixtures/errorRecovery/fixtures/_error_/interface-multiple-extends/snapshots/3-Alignment-Error.shot

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/ast-spec/tests/fixtures-with-differences-errors.shot

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/eslint-plugin/tests/rules/no-unused-vars/no-unused-vars.test.ts

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -360,25 +360,6 @@ export interface Bar extends baz.test {}
360360
code: `
361361
import test from 'test';
362362
import baz from 'baz';
363-
export interface Bar extends baz().test {}
364-
`,
365-
errors: [
366-
{
367-
column: 8,
368-
data: {
369-
action: 'defined',
370-
additional: '',
371-
varName: 'test',
372-
},
373-
line: 2,
374-
messageId: 'unusedVar',
375-
},
376-
],
377-
},
378-
{
379-
code: `
380-
import test from 'test';
381-
import baz from 'baz';
382363
export class Bar implements baz.test {}
383364
`,
384365
errors: [

packages/typescript-estree/src/convert.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ export interface ASTMaps {
7070
tsNodeToESTreeNodeMap: ParserWeakMap<TSNode, TSESTree.Node>;
7171
}
7272

73+
function isPropertyAccessEntityNameExpression(
74+
node: ts.Node,
75+
): node is ts.PropertyAccessEntityNameExpression {
76+
return (
77+
ts.isPropertyAccessExpression(node) &&
78+
ts.isIdentifier(node.name) &&
79+
isEntityNameExpression(node.expression)
80+
);
81+
}
82+
83+
function isEntityNameExpression(
84+
node: ts.Node,
85+
): node is ts.EntityNameExpression {
86+
return (
87+
node.kind === SyntaxKind.Identifier ||
88+
isPropertyAccessEntityNameExpression(node)
89+
);
90+
}
91+
7392
export class Converter {
7493
private allowPattern = false;
7594
private readonly ast: ts.SourceFile;
@@ -3024,6 +3043,7 @@ export class Converter {
30243043
const interfaceHeritageClauses = node.heritageClauses ?? [];
30253044
const interfaceExtends: TSESTree.TSInterfaceHeritage[] = [];
30263045

3046+
let seenExtendsClause = false;
30273047
for (const heritageClause of interfaceHeritageClauses) {
30283048
if (heritageClause.token !== SyntaxKind.ExtendsKeyword) {
30293049
this.#throwError(
@@ -3033,8 +3053,21 @@ export class Converter {
30333053
: 'Unexpected token.',
30343054
);
30353055
}
3056+
if (seenExtendsClause) {
3057+
this.#throwError(heritageClause, "'extends' clause already seen.");
3058+
}
3059+
seenExtendsClause = true;
30363060

30373061
for (const heritageType of heritageClause.types) {
3062+
if (
3063+
!isEntityNameExpression(heritageType.expression) ||
3064+
ts.isOptionalChain(heritageType.expression)
3065+
) {
3066+
this.#throwError(
3067+
heritageType,
3068+
'Interface declaration can only extend an identifier/qualified name with optional type arguments.',
3069+
);
3070+
}
30383071
interfaceExtends.push(
30393072
this.convertChild(
30403073
heritageType,

0 commit comments

Comments
 (0)