From 6586021b9748f93d628c3f43038738c19150bf81 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Sat, 23 Nov 2024 02:53:36 +0200 Subject: [PATCH 1/2] fix(60563): treat JSDoc implements tag as identifier in non-emitting heritage clause --- src/compiler/utilities.ts | 4 +++- .../reference/importTag23.errors.txt | 21 +++++++++++++++++++ tests/baselines/reference/importTag23.symbols | 19 +++++++++++++++++ tests/baselines/reference/importTag23.types | 19 +++++++++++++++++ tests/cases/conformance/jsdoc/importTag23.ts | 16 ++++++++++++++ 5 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/importTag23.errors.txt create mode 100644 tests/baselines/reference/importTag23.symbols create mode 100644 tests/baselines/reference/importTag23.types create mode 100644 tests/cases/conformance/jsdoc/importTag23.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d12ae297b81ad..0773b3622ee05 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -10483,6 +10483,7 @@ function isIdentifierInNonEmittingHeritageClause(node: Node): boolean { if (node.kind !== SyntaxKind.Identifier) return false; const heritageClause = findAncestor(node.parent, parent => { switch (parent.kind) { + case SyntaxKind.JSDocImplementsTag: case SyntaxKind.HeritageClause: return true; case SyntaxKind.PropertyAccessExpression: @@ -10492,7 +10493,8 @@ function isIdentifierInNonEmittingHeritageClause(node: Node): boolean { return "quit"; } }) as HeritageClause | undefined; - return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; + if (heritageClause === undefined) return false; + return isJSDocImplementsTag(heritageClause) || heritageClause.token === SyntaxKind.ImplementsKeyword || heritageClause.parent.kind === SyntaxKind.InterfaceDeclaration; } /** @internal */ diff --git a/tests/baselines/reference/importTag23.errors.txt b/tests/baselines/reference/importTag23.errors.txt new file mode 100644 index 0000000000000..03bedf5694d7e --- /dev/null +++ b/tests/baselines/reference/importTag23.errors.txt @@ -0,0 +1,21 @@ +/b.js(6,14): error TS2420: Class 'C' incorrectly implements interface 'I'. + Property 'foo' is missing in type 'C' but required in type 'I'. + + +==== /a.ts (0 errors) ==== + export interface I { + foo(): void; + } + +==== /b.js (1 errors) ==== + /** + * @import * as NS from './a' + */ + + /** @implements {NS.I} */ + export class C {} + ~ +!!! error TS2420: Class 'C' incorrectly implements interface 'I'. +!!! error TS2420: Property 'foo' is missing in type 'C' but required in type 'I'. +!!! related TS2728 /a.ts:2:5: 'foo' is declared here. + \ No newline at end of file diff --git a/tests/baselines/reference/importTag23.symbols b/tests/baselines/reference/importTag23.symbols new file mode 100644 index 0000000000000..0ef50ac35705a --- /dev/null +++ b/tests/baselines/reference/importTag23.symbols @@ -0,0 +1,19 @@ +//// [tests/cases/conformance/jsdoc/importTag23.ts] //// + +=== /a.ts === +export interface I { +>I : Symbol(I, Decl(a.ts, 0, 0)) + + foo(): void; +>foo : Symbol(I.foo, Decl(a.ts, 0, 20)) +} + +=== /b.js === +/** + * @import * as NS from './a' + */ + +/** @implements {NS.I} */ +export class C {} +>C : Symbol(C, Decl(b.js, 0, 0)) + diff --git a/tests/baselines/reference/importTag23.types b/tests/baselines/reference/importTag23.types new file mode 100644 index 0000000000000..e2752f504851d --- /dev/null +++ b/tests/baselines/reference/importTag23.types @@ -0,0 +1,19 @@ +//// [tests/cases/conformance/jsdoc/importTag23.ts] //// + +=== /a.ts === +export interface I { + foo(): void; +>foo : () => void +> : ^^^^^^ +} + +=== /b.js === +/** + * @import * as NS from './a' + */ + +/** @implements {NS.I} */ +export class C {} +>C : C +> : ^ + diff --git a/tests/cases/conformance/jsdoc/importTag23.ts b/tests/cases/conformance/jsdoc/importTag23.ts new file mode 100644 index 0000000000000..ad2f361fe36c6 --- /dev/null +++ b/tests/cases/conformance/jsdoc/importTag23.ts @@ -0,0 +1,16 @@ +// @checkJs: true +// @allowJs: true +// @noEmit: true + +// @filename: /a.ts +export interface I { + foo(): void; +} + +// @filename: /b.js +/** + * @import * as NS from './a' + */ + +/** @implements {NS.I} */ +export class C {} From 82e76ec26bac215d7dc19bd3b1097beaf4505b70 Mon Sep 17 00:00:00 2001 From: Oleksandr T Date: Fri, 17 Jan 2025 22:59:37 +0200 Subject: [PATCH 2/2] consider type only alias as valid for jsdoc context --- src/compiler/utilities.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 0773b3622ee05..17041d2e45b6b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -10454,6 +10454,7 @@ export function isValidBigIntString(s: string, roundTripOnly: boolean): boolean /** @internal */ export function isValidTypeOnlyAliasUseSite(useSite: Node): boolean { return !!(useSite.flags & NodeFlags.Ambient) + || isInJSDoc(useSite) || isPartOfTypeQuery(useSite) || isIdentifierInNonEmittingHeritageClause(useSite) || isPartOfPossiblyValidTypeOrAbstractComputedPropertyName(useSite) @@ -10483,7 +10484,6 @@ function isIdentifierInNonEmittingHeritageClause(node: Node): boolean { if (node.kind !== SyntaxKind.Identifier) return false; const heritageClause = findAncestor(node.parent, parent => { switch (parent.kind) { - case SyntaxKind.JSDocImplementsTag: case SyntaxKind.HeritageClause: return true; case SyntaxKind.PropertyAccessExpression: @@ -10493,8 +10493,7 @@ function isIdentifierInNonEmittingHeritageClause(node: Node): boolean { return "quit"; } }) as HeritageClause | undefined; - if (heritageClause === undefined) return false; - return isJSDocImplementsTag(heritageClause) || heritageClause.token === SyntaxKind.ImplementsKeyword || heritageClause.parent.kind === SyntaxKind.InterfaceDeclaration; + return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration; } /** @internal */