From e7c47aef68bb735a319f0d9bc0e425e488391d4b Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Wed, 14 Oct 2026 10:47:39 +0300 Subject: [PATCH 1/2] fix(64058): apply JSDoc extends arguments to heritage expressions --- tsc/internal/checker/checker.go | 28 +-- tsc/internal/checker/jsdoc.go | 32 ++++ tsc/internal/parser/parser_test.go | 62 +++++++ tsc/internal/parser/reparser.go | 20 ++- .../transformers/declarations/transform.go | 2 +- .../transformers/declarations/util.go | 13 -- .../conformance/extendsTag10.errors.txt | 33 ++++ .../reference/conformance/extendsTag10.js | 76 ++++++++ .../conformance/extendsTag10.symbols | 44 +++++ .../reference/conformance/extendsTag10.types | 54 ++++++ .../conformance/extendsTag11.errors.txt | 49 ++++++ .../conformance/extendsTag11.symbols | 70 ++++++++ .../reference/conformance/extendsTag11.types | 81 +++++++++ .../conformance/extendsTag12.errors.txt | 72 ++++++++ .../conformance/extendsTag12.symbols | 144 +++++++++++++++ .../reference/conformance/extendsTag12.types | 166 ++++++++++++++++++ .../conformance/extendsTag13.errors.txt | 80 +++++++++ .../conformance/extendsTag13.symbols | 107 +++++++++++ .../reference/conformance/extendsTag13.types | 116 ++++++++++++ .../reference/conformance/extendsTag7.js | 54 ++++++ .../reference/conformance/extendsTag7.symbols | 37 ++++ .../reference/conformance/extendsTag7.types | 40 +++++ .../reference/conformance/extendsTag8.symbols | 27 +++ .../reference/conformance/extendsTag8.types | 28 +++ .../conformance/extendsTag9.errors.txt | 36 ++++ .../reference/conformance/extendsTag9.symbols | 54 ++++++ .../reference/conformance/extendsTag9.types | 61 +++++++ .../cases/conformance/jsdoc/extendsTag10.ts | 27 +++ .../cases/conformance/jsdoc/extendsTag11.ts | 42 +++++ .../cases/conformance/jsdoc/extendsTag12.ts | 72 ++++++++ .../cases/conformance/jsdoc/extendsTag13.ts | 65 +++++++ .../cases/conformance/jsdoc/extendsTag7.ts | 24 +++ .../cases/conformance/jsdoc/extendsTag8.ts | 19 ++ .../cases/conformance/jsdoc/extendsTag9.ts | 32 ++++ 34 files changed, 1817 insertions(+), 50 deletions(-) create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag10.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag10.js create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag10.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag10.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag11.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag11.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag11.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag12.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag12.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag12.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag13.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag13.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag13.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag7.js create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag7.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag7.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag8.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag8.types create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag9.errors.txt create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag9.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag9.types create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag10.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag11.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag12.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag13.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag7.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag8.ts create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag9.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index b03cebf741b22..ea86a804031ce 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -4362,8 +4362,8 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) { baseTypes := c.getBaseTypes(classType) if len(baseTypes) != 0 { baseType := baseTypes[0] - c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType) baseConstructorType := c.getBaseConstructorTypeOfClass(classType) + c.checkJSDocAugmentsTagMatchesExtends(node, baseTypeNode, baseType) staticBaseType := c.getApparentType(baseConstructorType) c.checkBaseTypeAccessibility(staticBaseType, baseTypeNode) c.checkSourceElement(baseTypeNode.Expression()) @@ -4438,32 +4438,6 @@ func (c *Checker) checkClassLikeDeclaration(node *ast.Node) { c.checkPropertyInitialization(node) } -func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type) { - if !ast.IsInJSFile(node) { - return - } - file := ast.GetSourceFileOfNode(node) - for _, j := range node.EagerJSDoc(file) { - if j.AsJSDoc().Tags == nil { - continue - } - for _, tag := range j.AsJSDoc().Tags.Nodes { - if tag.Kind != ast.KindJSDocAugmentsTag { - continue - } - sourceTypeNode := tag.ClassName() - if c.isTypeIdenticalTo(c.getTypeFromTypeNode(sourceTypeNode), baseType) { - continue - } - targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression()) - sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression()) - if targetName != nil && sourceName != nil { - c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text()) - } - } - } -} - func (c *Checker) checkClassForStaticPropertyNameConflicts(node *ast.Node) { if c.compilerOptions.GetUseDefineForClassFields() { return diff --git a/tsc/internal/checker/jsdoc.go b/tsc/internal/checker/jsdoc.go index c8b5fb1adba44..da014a5c9f3b5 100644 --- a/tsc/internal/checker/jsdoc.go +++ b/tsc/internal/checker/jsdoc.go @@ -4,6 +4,7 @@ import ( "github.com/microsoft/TypeScript/tsc/internal/ast" "github.com/microsoft/TypeScript/tsc/internal/collections" "github.com/microsoft/TypeScript/tsc/internal/diagnostics" + "github.com/microsoft/TypeScript/tsc/internal/scanner" ) func (c *Checker) checkUnmatchedJSDocParameters(node *ast.Node) { @@ -98,3 +99,34 @@ func getAllJSDocTags(node *ast.Node) []*ast.Node { } return nil } + +func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNode *ast.ExpressionWithTypeArgumentsNode, baseType *Type) { + if !ast.IsInJSFile(node) { + return + } + file := ast.GetSourceFileOfNode(node) + for _, j := range node.EagerJSDoc(file) { + if j.AsJSDoc().Tags == nil { + continue + } + for _, tag := range j.AsJSDoc().Tags.Nodes { + if tag.Kind != ast.KindJSDocAugmentsTag { + continue + } + sourceTypeNode := tag.ClassName() + sourceType := c.getTypeFromTypeNode(sourceTypeNode) + sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression()) + if ast.IsEntityNameExpression(baseTypeNode.Expression()) { + if c.isTypeIdenticalTo(sourceType, baseType) { + continue + } + targetName := getIdentifierFromEntityNameExpression(baseTypeNode.Expression()) + if targetName != nil && sourceName != nil { + c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetName.Text()) + } + } else if sourceName != nil && !c.isTypeAssignableTo(baseType, sourceType) { + c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), scanner.GetTextOfNode(baseTypeNode.Expression())) + } + } + } +} diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index 8c68c97a462bd..27082afcb57b3 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -487,6 +487,68 @@ function foo(options) {}` assert.Equal(t, scanner.GetTokenPosOfNode(typeNode, file, false /*includeJSDoc*/), strings.Index(sourceText, "{{")+1) } +func TestJSDocAugmentsHeritage(t *testing.T) { + t.Parallel() + for _, test := range []struct { + name string + expression string + tagName string + augments bool + }{ + {"identifier", "A", "A", true}, + {"dotted name", "ns.A", "ns.A", true}, + {"call", "A.extend()", "A", true}, + {"mixin", "mixin(A)", "A", true}, + {"element access", `ns["A"]`, "A", true}, + {"conditional", "(cond ? A : A)", "A", true}, + {"parenthesized name", "(A)", "A", true}, + {"mismatched identifier", "A", "Other", false}, + {"mismatched dotted name", "ns.A", "ns.Other", false}, + } { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + sourceText := "/** @extends {" + test.tagName + "} */\nclass B extends " + test.expression + " {}" + opts := ast.SourceFileParseOptions{ + FileName: "/index.js", + PathKey: "/index.js", + } + file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS) + parsetestutil.CheckDiagnostics(t, file) + assert.Equal(t, len(file.Statements.Nodes), 1) + classB := file.Statements.Nodes[0] + assert.Assert(t, ast.IsClassDeclaration(classB)) + + baseType := ast.GetClassExtendsHeritageElement(classB) + assert.Assert(t, baseType != nil) + assert.Equal(t, scanner.GetTextOfNode(baseType.Expression()), test.expression) + + jsDocs := classB.JSDoc(file) + assert.Equal(t, len(jsDocs), 1) + tags := jsDocs[0].AsJSDoc().Tags + assert.Assert(t, tags != nil) + assert.Equal(t, len(tags.Nodes), 1) + tag := tags.Nodes[0] + assert.Assert(t, ast.IsJSDocAugmentsTag(tag)) + sourceTypeArguments := tag.ClassName().TypeArguments() + assert.Equal(t, len(sourceTypeArguments), 1) + + typeArguments := baseType.TypeArguments() + if test.augments { + assert.Equal(t, len(typeArguments), 1) + typeArgument := typeArguments[0] + assert.Equal(t, typeArgument.Kind, ast.KindStringKeyword) + assert.Assert(t, typeArgument.Flags&ast.NodeFlagsReparsed != 0) + assert.Equal(t, typeArgument.Parent, baseType) + assert.Equal(t, scanner.GetTextOfNode(typeArgument), "string") + assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), typeArgument) + } else { + assert.Equal(t, len(typeArguments), 0) + assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), sourceTypeArguments[0]) + } + }) + } +} + func TestSourceFilePositionMapWithNonASCIIStringLiteral(t *testing.T) { t.Parallel() sourceText := `const x = "─"; diff --git a/tsc/internal/parser/reparser.go b/tsc/internal/parser/reparser.go index bdebd40ade98d..418f48210488d 100644 --- a/tsc/internal/parser/reparser.go +++ b/tsc/internal/parser/reparser.go @@ -597,21 +597,23 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) }); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 { target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments() source := tag.ClassName().AsExpressionWithTypeArguments() - if ast.HasSamePropertyAccessName(target.Expression, source.Expression) { - if target.TypeArguments == nil && source.TypeArguments != nil { - newArguments := p.nodeSliceArena.NewSlice(len(source.TypeArguments.Nodes)) - for i, arg := range source.TypeArguments.Nodes { - newArguments[i] = p.addDeepCloneReparse(arg) - } - target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments) - p.finishMutatedNode(target.AsNode()) - } + if target.TypeArguments == nil && source.TypeArguments != nil && (!ast.IsEntityNameExpression(target.Expression) || ast.HasSamePropertyAccessName(target.Expression, source.Expression)) { + p.setReparsedTypeArguments(target, source.TypeArguments) } } } } } +func (p *Parser) setReparsedTypeArguments(target *ast.ExpressionWithTypeArguments, source *ast.NodeList) { + typeArguments := p.nodeSliceArena.NewSlice(len(source.Nodes)) + for i, typeArgument := range source.Nodes { + typeArguments[i] = p.addDeepCloneReparse(typeArgument) + } + target.TypeArguments = p.newNodeList(source.Loc, typeArguments) + p.finishMutatedNode(target.AsNode()) +} + func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterOrPropertyTag) *ast.Node { var questionToken *ast.Node if parameter.IsBracketed || parameter.TypeExpression != nil && parameter.TypeExpression.Type().Kind == ast.KindJSDocOptionalType { diff --git a/tsc/internal/transformers/declarations/transform.go b/tsc/internal/transformers/declarations/transform.go index f2bb66a6803cb..33ea56bd370e9 100644 --- a/tsc/internal/transformers/declarations/transform.go +++ b/tsc/internal/transformers/declarations/transform.go @@ -2007,7 +2007,7 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl members := tx.buildClassMembers(input.AsNode(), extraMembers...) - extendsClause := getEffectiveBaseTypeNode(input.AsNode()) + extendsClause := ast.GetClassExtendsHeritageElement(input.AsNode()) if extendsClause != nil && !ast.IsEntityNameExpression(extendsClause.AsExpressionWithTypeArguments().Expression) && extendsClause.AsExpressionWithTypeArguments().Expression.Kind != ast.KindNullKeyword { tx.tracker.ReportInferenceFallback(extendsClause.AsExpressionWithTypeArguments().Expression) // Add an isolated declarations error on this extends clause diff --git a/tsc/internal/transformers/declarations/util.go b/tsc/internal/transformers/declarations/util.go index 2f05e33035821..12bfff9600c0d 100644 --- a/tsc/internal/transformers/declarations/util.go +++ b/tsc/internal/transformers/declarations/util.go @@ -161,19 +161,6 @@ func shouldEmitFunctionProperties(input *ast.FunctionDeclaration) bool { }) } -func getEffectiveBaseTypeNode(node *ast.Node) *ast.Node { - baseType := ast.GetClassExtendsHeritageElement(node) - // !!! TODO: JSDoc support - // if (baseType && isInJSFile(node)) { - // // Prefer an @augments tag because it may have type parameters. - // const tag = getJSDocAugmentsTag(node); - // if (tag) { - // return tag.class; - // } - // } - return baseType -} - func isScopeMarker(node *ast.Node) bool { return ast.IsExportAssignment(node) || ast.IsExportDeclaration(node) } diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag10.errors.txt b/tsc/testdata/baselines/reference/conformance/extendsTag10.errors.txt new file mode 100644 index 0000000000000..fac7780b448b9 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag10.errors.txt @@ -0,0 +1,33 @@ +a.js(17,17): error TS2315: Type '(Anonymous class)' is not generic. +a.js(19,23): error TS2339: Property 'value' does not exist on type 'B'. +a.js(20,23): error TS2339: Property 'extra' does not exist on type 'B'. + + +==== a.js (3 errors) ==== + /** @template T */ + class A { + /** @returns {T} */ + get value() { + throw new Error(); + } + } + + /** @param {any} Base */ + function mixin(Base) { + return class extends Base { + extra = 1; + }; + } + + /** @extends {A} */ + class B extends mixin(A) {} + ~~~~~~~~ +!!! error TS2315: Type '(Anonymous class)' is not generic. + + const value = new B().value; + ~~~~~ +!!! error TS2339: Property 'value' does not exist on type 'B'. + const extra = new B().extra; + ~~~~~ +!!! error TS2339: Property 'extra' does not exist on type 'B'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag10.js b/tsc/testdata/baselines/reference/conformance/extendsTag10.js new file mode 100644 index 0000000000000..f347bdf32b3e7 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag10.js @@ -0,0 +1,76 @@ +//// [tests/cases/conformance/jsdoc/extendsTag10.ts] //// + +//// [a.js] +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } +} + +/** @param {any} Base */ +function mixin(Base) { + return class extends Base { + extra = 1; + }; +} + +/** @extends {A} */ +class B extends mixin(A) {} + +const value = new B().value; +const extra = new B().extra; + + +//// [a.js] +"use strict"; +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } +} +/** @param {any} Base */ +function mixin(Base) { + return class extends Base { + constructor() { + super(...arguments); + this.extra = 1; + } + }; +} +/** @extends {A} */ +class B extends mixin(A) { +} +const value = new B().value; +const extra = new B().extra; + + +//// [a.d.ts] +/** @template T */ +declare class A { + /** @returns {T} */ + get value(): T; +} +/** @param {any} Base */ +declare function mixin(Base: any): { + new (): { + [x: string]: any; + extra: number; + }; + [x: string]: any; +}; +declare const B_base: { + new (): { + [x: string]: any; + extra: number; + }; + [x: string]: any; +}; +/** @extends {A} */ +declare class B extends B_base { +} +declare const value: any; +declare const extra: any; diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag10.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag10.symbols new file mode 100644 index 0000000000000..9e970159307db --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag10.symbols @@ -0,0 +1,44 @@ +//// [tests/cases/conformance/jsdoc/extendsTag10.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @returns {T} */ + get value() { +>value : Symbol(A.value, Decl(a.js, 1, 9)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +/** @param {any} Base */ +function mixin(Base) { +>mixin : Symbol(mixin, Decl(a.js, 6, 1)) +>Base : Symbol(Base, Decl(a.js, 9, 15)) + + return class extends Base { +>Base : Symbol(Base, Decl(a.js, 9, 15)) + + extra = 1; +>extra : Symbol((Anonymous class).extra, Decl(a.js, 10, 31)) + + }; +} + +/** @extends {A} */ +class B extends mixin(A) {} +>B : Symbol(B, Decl(a.js, 13, 1)) +>mixin : Symbol(mixin, Decl(a.js, 6, 1)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +const value = new B().value; +>value : Symbol(value, Decl(a.js, 18, 5)) +>B : Symbol(B, Decl(a.js, 13, 1)) + +const extra = new B().extra; +>extra : Symbol(extra, Decl(a.js, 19, 5)) +>B : Symbol(B, Decl(a.js, 13, 1)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag10.types b/tsc/testdata/baselines/reference/conformance/extendsTag10.types new file mode 100644 index 0000000000000..359ace0d7582b --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag10.types @@ -0,0 +1,54 @@ +//// [tests/cases/conformance/jsdoc/extendsTag10.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @returns {T} */ + get value() { +>value : T + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } +} + +/** @param {any} Base */ +function mixin(Base) { +>mixin : (Base: any) => typeof (Anonymous class) +>Base : any + + return class extends Base { +>class extends Base { extra = 1; } : typeof (Anonymous class) +>Base : any + + extra = 1; +>extra : number +>1 : 1 + + }; +} + +/** @extends {A} */ +class B extends mixin(A) {} +>B : B +>mixin(A) : typeof (Anonymous class) +>mixin : (Base: any) => typeof (Anonymous class) +>A : typeof A + +const value = new B().value; +>value : any +>new B().value : any +>new B() : B +>B : typeof B +>value : any + +const extra = new B().extra; +>extra : any +>new B().extra : any +>new B() : B +>B : typeof B +>extra : any + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag11.errors.txt b/tsc/testdata/baselines/reference/conformance/extendsTag11.errors.txt new file mode 100644 index 0000000000000..bd9db178f4833 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag11.errors.txt @@ -0,0 +1,49 @@ +a.js(18,7): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. +a.js(23,15): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. +a.js(36,22): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + + +==== a.js (3 errors) ==== + /** @template T */ + class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } + + /** @returns {typeof A} */ + static extend() { + return this; + } + } + + /** @extends {A} */ + class B extends A.extend() {} + + new B("ok"); + new B(1); + ~ +!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. + + /** @extends {A} */ + class C extends A.extend() { + constructor() { + super(1); + ~ +!!! error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'. + } + } + + /** + * @param {number} required + * @returns {typeof A} + */ + function getA(required) { + return A; + } + + /** @extends {A} */ + class D extends getA("wrong") {} + ~~~~~~~ +!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag11.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag11.symbols new file mode 100644 index 0000000000000..886f9ac64cf2f --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag11.symbols @@ -0,0 +1,70 @@ +//// [tests/cases/conformance/jsdoc/extendsTag11.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @param {T} value */ + constructor(value) { +>value : Symbol(value, Decl(a.js, 3, 16)) + + this.value = value; +>this.value : Symbol(A.value, Decl(a.js, 3, 24)) +>this : Symbol(A, Decl(a.js, 0, 0)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) +>value : Symbol(value, Decl(a.js, 3, 16)) + } + + /** @returns {typeof A} */ + static extend() { +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + + return this; +>this : Symbol(A, Decl(a.js, 0, 0)) + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : Symbol(B, Decl(a.js, 11, 1)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +new B("ok"); +>B : Symbol(B, Decl(a.js, 11, 1)) + +new B(1); +>B : Symbol(B, Decl(a.js, 11, 1)) + +/** @extends {A} */ +class C extends A.extend() { +>C : Symbol(C, Decl(a.js, 17, 9)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + + constructor() { + super(1); +>super : Symbol(A, Decl(a.js, 0, 0)) + } +} + +/** + * @param {number} required + * @returns {typeof A} + */ +function getA(required) { +>getA : Symbol(getA, Decl(a.js, 24, 1)) +>required : Symbol(required, Decl(a.js, 30, 14)) + + return A; +>A : Symbol(A, Decl(a.js, 0, 0)) +} + +/** @extends {A} */ +class D extends getA("wrong") {} +>D : Symbol(D, Decl(a.js, 32, 1)) +>getA : Symbol(getA, Decl(a.js, 24, 1)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag11.types b/tsc/testdata/baselines/reference/conformance/extendsTag11.types new file mode 100644 index 0000000000000..e0fb705e38eee --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag11.types @@ -0,0 +1,81 @@ +//// [tests/cases/conformance/jsdoc/extendsTag11.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @param {T} value */ + constructor(value) { +>value : T + + this.value = value; +>this.value = value : T +>this.value : any +>this : this +>value : any +>value : T + } + + /** @returns {typeof A} */ + static extend() { +>extend : () => typeof A + + return this; +>this : typeof A + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : B +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + +new B("ok"); +>new B("ok") : B +>B : typeof B +>"ok" : "ok" + +new B(1); +>new B(1) : B +>B : typeof B +>1 : 1 + +/** @extends {A} */ +class C extends A.extend() { +>C : C +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + + constructor() { + super(1); +>super(1) : void +>super : typeof A +>1 : 1 + } +} + +/** + * @param {number} required + * @returns {typeof A} + */ +function getA(required) { +>getA : (required: number) => typeof A +>required : number + + return A; +>A : typeof A +} + +/** @extends {A} */ +class D extends getA("wrong") {} +>D : D +>getA("wrong") : A +>getA : (required: number) => typeof A +>"wrong" : "wrong" + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag12.errors.txt b/tsc/testdata/baselines/reference/conformance/extendsTag12.errors.txt new file mode 100644 index 0000000000000..222cf0ef6a9a7 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag12.errors.txt @@ -0,0 +1,72 @@ +a.js(49,15): error TS8023: JSDoc '@extends Other' does not match the 'extends mixin(A)' clause. + + +==== a.js (1 errors) ==== + /** @template T */ + class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } + } + + /** + * @template T + * @extends {A} + */ + class Sub extends A { + /** @returns {typeof Sub} */ + static extend() { + return this; + } + } + + /** @template T */ + class Other { + /** @param {T} other */ + constructor(other) { + this.other = other; + } + } + + /** + * @template {new (...args: any[]) => any} C + * @param {C} Base + */ + function mixin(Base) { + return class extends Base { + extra = 1; + }; + } + + const ns = { A }; + /** @type {boolean} */ + let cond = true; + + /** @extends {A} */ + class ViaMixin extends mixin(A) {} + /** @type {string} */ + const viaMixin = new ViaMixin("ok").value; + /** @type {number} */ + const viaMixinExtra = new ViaMixin("ok").extra; + + /** @extends {Other} */ + ~~~~~ +!!! error TS8023: JSDoc '@extends Other' does not match the 'extends mixin(A)' clause. + class MismatchedMixin extends mixin(A) {} + + /** @extends {A} */ + class ViaSubclass extends Sub.extend() {} + /** @type {string} */ + const viaSubclass = new ViaSubclass("ok").value; + + /** @extends {A} */ + class ViaElementAccess extends ns["A"] {} + /** @type {string} */ + const viaElementAccess = new ViaElementAccess("ok").value; + + /** @extends {A} */ + class ViaConditional extends (cond ? A : A) {} + /** @type {string} */ + const viaConditional = new ViaConditional("ok").value; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag12.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag12.symbols new file mode 100644 index 0000000000000..a3f2188b85fc1 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag12.symbols @@ -0,0 +1,144 @@ +//// [tests/cases/conformance/jsdoc/extendsTag12.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @param {T} value */ + constructor(value) { +>value : Symbol(value, Decl(a.js, 3, 16)) + + this.value = value; +>this.value : Symbol(A.value, Decl(a.js, 3, 24)) +>this : Symbol(A, Decl(a.js, 0, 0)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) +>value : Symbol(value, Decl(a.js, 3, 16)) + } +} + +/** + * @template T + * @extends {A} + */ +class Sub extends A { +>Sub : Symbol(Sub, Decl(a.js, 6, 1)) +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @returns {typeof Sub} */ + static extend() { +>extend : Symbol(Sub.extend, Decl(a.js, 12, 21)) + + return this; +>this : Symbol(Sub, Decl(a.js, 6, 1)) + } +} + +/** @template T */ +class Other { +>Other : Symbol(Other, Decl(a.js, 17, 1)) + + /** @param {T} other */ + constructor(other) { +>other : Symbol(other, Decl(a.js, 22, 16)) + + this.other = other; +>this.other : Symbol(Other.other, Decl(a.js, 22, 24)) +>this : Symbol(Other, Decl(a.js, 17, 1)) +>other : Symbol(Other.other, Decl(a.js, 22, 24)) +>other : Symbol(other, Decl(a.js, 22, 16)) + } +} + +/** + * @template {new (...args: any[]) => any} C + * @param {C} Base + */ +function mixin(Base) { +>mixin : Symbol(mixin, Decl(a.js, 25, 1)) +>Base : Symbol(Base, Decl(a.js, 31, 15)) + + return class extends Base { +>Base : Symbol(Base, Decl(a.js, 31, 15)) + + extra = 1; +>extra : Symbol((Anonymous class).extra, Decl(a.js, 32, 31)) + + }; +} + +const ns = { A }; +>ns : Symbol(ns, Decl(a.js, 37, 5)) +>A : Symbol(A, Decl(a.js, 37, 12)) + +/** @type {boolean} */ +let cond = true; +>cond : Symbol(cond, Decl(a.js, 39, 3)) + +/** @extends {A} */ +class ViaMixin extends mixin(A) {} +>ViaMixin : Symbol(ViaMixin, Decl(a.js, 39, 16)) +>mixin : Symbol(mixin, Decl(a.js, 25, 1)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** @type {string} */ +const viaMixin = new ViaMixin("ok").value; +>viaMixin : Symbol(viaMixin, Decl(a.js, 44, 5)) +>new ViaMixin("ok").value : Symbol(A.value, Decl(a.js, 3, 24)) +>ViaMixin : Symbol(ViaMixin, Decl(a.js, 39, 16)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) + +/** @type {number} */ +const viaMixinExtra = new ViaMixin("ok").extra; +>viaMixinExtra : Symbol(viaMixinExtra, Decl(a.js, 46, 5)) +>new ViaMixin("ok").extra : Symbol((Anonymous class).extra, Decl(a.js, 32, 31)) +>ViaMixin : Symbol(ViaMixin, Decl(a.js, 39, 16)) +>extra : Symbol((Anonymous class).extra, Decl(a.js, 32, 31)) + +/** @extends {Other} */ +class MismatchedMixin extends mixin(A) {} +>MismatchedMixin : Symbol(MismatchedMixin, Decl(a.js, 46, 47)) +>mixin : Symbol(mixin, Decl(a.js, 25, 1)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** @extends {A} */ +class ViaSubclass extends Sub.extend() {} +>ViaSubclass : Symbol(ViaSubclass, Decl(a.js, 49, 41)) +>Sub.extend : Symbol(Sub.extend, Decl(a.js, 12, 21)) +>Sub : Symbol(Sub, Decl(a.js, 6, 1)) +>extend : Symbol(Sub.extend, Decl(a.js, 12, 21)) + +/** @type {string} */ +const viaSubclass = new ViaSubclass("ok").value; +>viaSubclass : Symbol(viaSubclass, Decl(a.js, 54, 5)) +>new ViaSubclass("ok").value : Symbol(A.value, Decl(a.js, 3, 24)) +>ViaSubclass : Symbol(ViaSubclass, Decl(a.js, 49, 41)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) + +/** @extends {A} */ +class ViaElementAccess extends ns["A"] {} +>ViaElementAccess : Symbol(ViaElementAccess, Decl(a.js, 54, 48)) +>ns : Symbol(ns, Decl(a.js, 37, 5)) +>"A" : Symbol(A, Decl(a.js, 37, 12)) + +/** @type {string} */ +const viaElementAccess = new ViaElementAccess("ok").value; +>viaElementAccess : Symbol(viaElementAccess, Decl(a.js, 59, 5)) +>new ViaElementAccess("ok").value : Symbol(A.value, Decl(a.js, 3, 24)) +>ViaElementAccess : Symbol(ViaElementAccess, Decl(a.js, 54, 48)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) + +/** @extends {A} */ +class ViaConditional extends (cond ? A : A) {} +>ViaConditional : Symbol(ViaConditional, Decl(a.js, 59, 58)) +>cond : Symbol(cond, Decl(a.js, 39, 3)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** @type {string} */ +const viaConditional = new ViaConditional("ok").value; +>viaConditional : Symbol(viaConditional, Decl(a.js, 64, 5)) +>new ViaConditional("ok").value : Symbol(A.value, Decl(a.js, 3, 24)) +>ViaConditional : Symbol(ViaConditional, Decl(a.js, 59, 58)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag12.types b/tsc/testdata/baselines/reference/conformance/extendsTag12.types new file mode 100644 index 0000000000000..1ae7dfa5a8fda --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag12.types @@ -0,0 +1,166 @@ +//// [tests/cases/conformance/jsdoc/extendsTag12.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @param {T} value */ + constructor(value) { +>value : T + + this.value = value; +>this.value = value : T +>this.value : any +>this : this +>value : any +>value : T + } +} + +/** + * @template T + * @extends {A} + */ +class Sub extends A { +>Sub : Sub +>A : A + + /** @returns {typeof Sub} */ + static extend() { +>extend : () => typeof Sub + + return this; +>this : typeof Sub + } +} + +/** @template T */ +class Other { +>Other : Other + + /** @param {T} other */ + constructor(other) { +>other : T + + this.other = other; +>this.other = other : T +>this.other : any +>this : this +>other : any +>other : T + } +} + +/** + * @template {new (...args: any[]) => any} C + * @param {C} Base + */ +function mixin(Base) { +>mixin : any>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +>Base : C + + return class extends Base { +>class extends Base { extra = 1; } : { new (...args: any[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +>Base : C + + extra = 1; +>extra : number +>1 : 1 + + }; +} + +const ns = { A }; +>ns : { A: typeof A; } +>{ A } : { A: typeof A; } +>A : typeof A + +/** @type {boolean} */ +let cond = true; +>cond : boolean +>true : true + +/** @extends {A} */ +class ViaMixin extends mixin(A) {} +>ViaMixin : ViaMixin +>mixin(A) : mixin.(Anonymous class) & A +>mixin : any>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +>A : typeof A + +/** @type {string} */ +const viaMixin = new ViaMixin("ok").value; +>viaMixin : string +>new ViaMixin("ok").value : string +>new ViaMixin("ok") : ViaMixin +>ViaMixin : typeof ViaMixin +>"ok" : "ok" +>value : string + +/** @type {number} */ +const viaMixinExtra = new ViaMixin("ok").extra; +>viaMixinExtra : number +>new ViaMixin("ok").extra : number +>new ViaMixin("ok") : ViaMixin +>ViaMixin : typeof ViaMixin +>"ok" : "ok" +>extra : number + +/** @extends {Other} */ +class MismatchedMixin extends mixin(A) {} +>MismatchedMixin : MismatchedMixin +>mixin(A) : mixin.(Anonymous class) & A +>mixin : any>(Base: C) => { new (...args: any[]): (Anonymous class); prototype: mixin.(Anonymous class); } & C +>A : typeof A + +/** @extends {A} */ +class ViaSubclass extends Sub.extend() {} +>ViaSubclass : ViaSubclass +>Sub.extend() : Sub +>Sub.extend : () => typeof Sub +>Sub : typeof Sub +>extend : () => typeof Sub + +/** @type {string} */ +const viaSubclass = new ViaSubclass("ok").value; +>viaSubclass : string +>new ViaSubclass("ok").value : string +>new ViaSubclass("ok") : ViaSubclass +>ViaSubclass : typeof ViaSubclass +>"ok" : "ok" +>value : string + +/** @extends {A} */ +class ViaElementAccess extends ns["A"] {} +>ViaElementAccess : ViaElementAccess +>ns["A"] : A +>ns : { A: typeof A; } +>"A" : "A" + +/** @type {string} */ +const viaElementAccess = new ViaElementAccess("ok").value; +>viaElementAccess : string +>new ViaElementAccess("ok").value : string +>new ViaElementAccess("ok") : ViaElementAccess +>ViaElementAccess : typeof ViaElementAccess +>"ok" : "ok" +>value : string + +/** @extends {A} */ +class ViaConditional extends (cond ? A : A) {} +>ViaConditional : ViaConditional +>(cond ? A : A) : A +>cond ? A : A : typeof A +>cond : true +>A : typeof A +>A : typeof A + +/** @type {string} */ +const viaConditional = new ViaConditional("ok").value; +>viaConditional : string +>new ViaConditional("ok").value : string +>new ViaConditional("ok") : ViaConditional +>ViaConditional : typeof ViaConditional +>"ok" : "ok" +>value : string + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag13.errors.txt b/tsc/testdata/baselines/reference/conformance/extendsTag13.errors.txt new file mode 100644 index 0000000000000..8dafa116ab8ec --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag13.errors.txt @@ -0,0 +1,80 @@ +a.js(26,14): error TS8023: JSDoc '@extends Other' does not match the 'extends A' clause. +a.js(32,14): error TS8023: JSDoc '@extends Other' does not match the 'extends A.extend()' clause. +a.js(33,15): error TS8023: JSDoc '@augments Other' does not match the 'extends A.extend()' clause. +a.js(38,15): error TS8023: JSDoc '@extends Other' does not match the 'extends A' clause. +a.js(43,16): error TS8023: JSDoc '@augments Other' does not match the 'extends ns["A"]' clause. +a.js(47,14): error TS8023: JSDoc '@extends Other' does not match the 'extends A.extend()' clause. + + +==== a.js (6 errors) ==== + /** @template T */ + class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } + + /** @returns {typeof A} */ + static extend() { + return this; + } + } + + /** @template T */ + class Other { + /** @param {T} other */ + constructor(other) { + this.other = other; + } + } + + const ns = { A }; + + /** + * @extends {A} + * @extends {Other} + ~~~~~ +!!! error TS8023: JSDoc '@extends Other' does not match the 'extends A' clause. + */ + class Direct extends A {} + + /** + * @extends {A} + * @extends {Other} + ~~~~~ +!!! error TS8023: JSDoc '@extends Other' does not match the 'extends A.extend()' clause. + * @augments {Other} + ~~~~~ +!!! error TS8023: JSDoc '@augments Other' does not match the 'extends A.extend()' clause. + */ + class Call extends A.extend() {} + + /** @extends {A} */ + /** @extends {Other} */ + ~~~~~ +!!! error TS8023: JSDoc '@extends Other' does not match the 'extends A' clause. + /** @extends {A} */ + class SeparateDirect extends A {} + + /** @extends {A} */ + /** @augments {Other} */ + ~~~~~ +!!! error TS8023: JSDoc '@augments Other' does not match the 'extends ns["A"]' clause. + class SeparateElement extends ns["A"] {} + + /** + * @extends {Other} + ~~~~~ +!!! error TS8023: JSDoc '@extends Other' does not match the 'extends A.extend()' clause. + * @extends {A} + */ + class FirstMismatch extends A.extend() {} + + /** + * @extends {A} + * @augments {A} + */ + class CompatibleTags extends A.extend() {} + /** @type {string} */ + const value = new CompatibleTags("ok").value; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag13.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag13.symbols new file mode 100644 index 0000000000000..d2300b6bf9ac3 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag13.symbols @@ -0,0 +1,107 @@ +//// [tests/cases/conformance/jsdoc/extendsTag13.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @param {T} value */ + constructor(value) { +>value : Symbol(value, Decl(a.js, 3, 16)) + + this.value = value; +>this.value : Symbol(A.value, Decl(a.js, 3, 24)) +>this : Symbol(A, Decl(a.js, 0, 0)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) +>value : Symbol(value, Decl(a.js, 3, 16)) + } + + /** @returns {typeof A} */ + static extend() { +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + + return this; +>this : Symbol(A, Decl(a.js, 0, 0)) + } +} + +/** @template T */ +class Other { +>Other : Symbol(Other, Decl(a.js, 11, 1)) + + /** @param {T} other */ + constructor(other) { +>other : Symbol(other, Decl(a.js, 16, 16)) + + this.other = other; +>this.other : Symbol(Other.other, Decl(a.js, 16, 24)) +>this : Symbol(Other, Decl(a.js, 11, 1)) +>other : Symbol(Other.other, Decl(a.js, 16, 24)) +>other : Symbol(other, Decl(a.js, 16, 16)) + } +} + +const ns = { A }; +>ns : Symbol(ns, Decl(a.js, 21, 5)) +>A : Symbol(A, Decl(a.js, 21, 12)) + +/** + * @extends {A} + * @extends {Other} + */ +class Direct extends A {} +>Direct : Symbol(Direct, Decl(a.js, 21, 17)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** + * @extends {A} + * @extends {Other} + * @augments {Other} + */ +class Call extends A.extend() {} +>Call : Symbol(Call, Decl(a.js, 27, 25)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +/** @extends {A} */ +/** @extends {Other} */ +/** @extends {A} */ +class SeparateDirect extends A {} +>SeparateDirect : Symbol(SeparateDirect, Decl(a.js, 34, 32)) +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** @extends {A} */ +/** @augments {Other} */ +class SeparateElement extends ns["A"] {} +>SeparateElement : Symbol(SeparateElement, Decl(a.js, 39, 33)) +>ns : Symbol(ns, Decl(a.js, 21, 5)) +>"A" : Symbol(A, Decl(a.js, 21, 12)) + +/** + * @extends {Other} + * @extends {A} + */ +class FirstMismatch extends A.extend() {} +>FirstMismatch : Symbol(FirstMismatch, Decl(a.js, 43, 40)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +/** + * @extends {A} + * @augments {A} + */ +class CompatibleTags extends A.extend() {} +>CompatibleTags : Symbol(CompatibleTags, Decl(a.js, 49, 41)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +/** @type {string} */ +const value = new CompatibleTags("ok").value; +>value : Symbol(value, Decl(a.js, 57, 5)) +>new CompatibleTags("ok").value : Symbol(A.value, Decl(a.js, 3, 24)) +>CompatibleTags : Symbol(CompatibleTags, Decl(a.js, 49, 41)) +>value : Symbol(A.value, Decl(a.js, 3, 24)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag13.types b/tsc/testdata/baselines/reference/conformance/extendsTag13.types new file mode 100644 index 0000000000000..e031929665c09 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag13.types @@ -0,0 +1,116 @@ +//// [tests/cases/conformance/jsdoc/extendsTag13.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @param {T} value */ + constructor(value) { +>value : T + + this.value = value; +>this.value = value : T +>this.value : any +>this : this +>value : any +>value : T + } + + /** @returns {typeof A} */ + static extend() { +>extend : () => typeof A + + return this; +>this : typeof A + } +} + +/** @template T */ +class Other { +>Other : Other + + /** @param {T} other */ + constructor(other) { +>other : T + + this.other = other; +>this.other = other : T +>this.other : any +>this : this +>other : any +>other : T + } +} + +const ns = { A }; +>ns : { A: typeof A; } +>{ A } : { A: typeof A; } +>A : typeof A + +/** + * @extends {A} + * @extends {Other} + */ +class Direct extends A {} +>Direct : Direct +>A : A + +/** + * @extends {A} + * @extends {Other} + * @augments {Other} + */ +class Call extends A.extend() {} +>Call : Call +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + +/** @extends {A} */ +/** @extends {Other} */ +/** @extends {A} */ +class SeparateDirect extends A {} +>SeparateDirect : SeparateDirect +>A : A + +/** @extends {A} */ +/** @augments {Other} */ +class SeparateElement extends ns["A"] {} +>SeparateElement : SeparateElement +>ns["A"] : A +>ns : { A: typeof A; } +>"A" : "A" + +/** + * @extends {Other} + * @extends {A} + */ +class FirstMismatch extends A.extend() {} +>FirstMismatch : FirstMismatch +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + +/** + * @extends {A} + * @augments {A} + */ +class CompatibleTags extends A.extend() {} +>CompatibleTags : CompatibleTags +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + +/** @type {string} */ +const value = new CompatibleTags("ok").value; +>value : string +>new CompatibleTags("ok").value : string +>new CompatibleTags("ok") : CompatibleTags +>CompatibleTags : typeof CompatibleTags +>"ok" : "ok" +>value : string + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag7.js b/tsc/testdata/baselines/reference/conformance/extendsTag7.js new file mode 100644 index 0000000000000..03a22db1d8266 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag7.js @@ -0,0 +1,54 @@ +//// [tests/cases/conformance/jsdoc/extendsTag7.ts] //// + +//// [a.js] +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } + + /** @returns {typeof A} */ + static extend() { + return this; + } +} + +/** @extends {A} */ +class B extends A.extend() {} + +const value = new B().value; + + +//// [a.js] +"use strict"; +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } + /** @returns {typeof A} */ + static extend() { + return this; + } +} +/** @extends {A} */ +class B extends A.extend() { +} +const value = new B().value; + + +//// [a.d.ts] +/** @template T */ +declare class A { + /** @returns {T} */ + get value(): T; + /** @returns {typeof A} */ + static extend(): typeof A; +} +declare const B_base: typeof A; +/** @extends {A} */ +declare class B extends B_base { +} +declare const value: string; diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag7.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag7.symbols new file mode 100644 index 0000000000000..20349b715c45d --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag7.symbols @@ -0,0 +1,37 @@ +//// [tests/cases/conformance/jsdoc/extendsTag7.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @returns {T} */ + get value() { +>value : Symbol(A.value, Decl(a.js, 1, 9)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + + /** @returns {typeof A} */ + static extend() { +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + + return this; +>this : Symbol(A, Decl(a.js, 0, 0)) + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : Symbol(B, Decl(a.js, 11, 1)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +const value = new B().value; +>value : Symbol(value, Decl(a.js, 16, 5)) +>new B().value : Symbol(A.value, Decl(a.js, 1, 9)) +>B : Symbol(B, Decl(a.js, 11, 1)) +>value : Symbol(A.value, Decl(a.js, 1, 9)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag7.types b/tsc/testdata/baselines/reference/conformance/extendsTag7.types new file mode 100644 index 0000000000000..50b3b50060c01 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag7.types @@ -0,0 +1,40 @@ +//// [tests/cases/conformance/jsdoc/extendsTag7.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @returns {T} */ + get value() { +>value : T + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } + + /** @returns {typeof A} */ + static extend() { +>extend : () => typeof A + + return this; +>this : typeof A + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : B +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + +const value = new B().value; +>value : string +>new B().value : string +>new B() : B +>B : typeof B +>value : string + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag8.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag8.symbols new file mode 100644 index 0000000000000..8b10f3499d474 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag8.symbols @@ -0,0 +1,27 @@ +//// [tests/cases/conformance/jsdoc/extendsTag8.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @returns {typeof A} */ + static extend() { +>extend : Symbol(A.extend, Decl(a.js, 1, 9)) + + return this; +>this : Symbol(A, Decl(a.js, 0, 0)) + } +} + +/** @template T */ +class C {} +>C : Symbol(C, Decl(a.js, 6, 1)) + +/** @extends {C} */ +class B extends A.extend() {} +>B : Symbol(B, Decl(a.js, 9, 10)) +>A.extend : Symbol(A.extend, Decl(a.js, 1, 9)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 1, 9)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag8.types b/tsc/testdata/baselines/reference/conformance/extendsTag8.types new file mode 100644 index 0000000000000..4c71958d321c1 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag8.types @@ -0,0 +1,28 @@ +//// [tests/cases/conformance/jsdoc/extendsTag8.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @returns {typeof A} */ + static extend() { +>extend : () => typeof A + + return this; +>this : typeof A + } +} + +/** @template T */ +class C {} +>C : C + +/** @extends {C} */ +class B extends A.extend() {} +>B : B +>A.extend() : A +>A.extend : () => typeof A +>A : typeof A +>extend : () => typeof A + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag9.errors.txt b/tsc/testdata/baselines/reference/conformance/extendsTag9.errors.txt new file mode 100644 index 0000000000000..b2521bb3eae28 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag9.errors.txt @@ -0,0 +1,36 @@ +a.js(22,15): error TS8023: JSDoc '@extends A' does not match the 'extends A.extend()' clause. +a.js(25,19): error TS2339: Property 'a' does not exist on type 'B'. + + +==== a.js (2 errors) ==== + /** @template T */ + class A { + /** @returns {T} */ + get a() { + throw new Error(); + } + + /** @returns {typeof C} */ + static extend() { + return C; + } + } + + /** @template T */ + class C { + /** @returns {T} */ + get c() { + throw new Error(); + } + } + + /** @extends {A} */ + ~ +!!! error TS8023: JSDoc '@extends A' does not match the 'extends A.extend()' clause. + class B extends A.extend() {} + + const a = new B().a; + ~ +!!! error TS2339: Property 'a' does not exist on type 'B'. + const c = new B().c; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag9.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag9.symbols new file mode 100644 index 0000000000000..6ff2bd61bdd27 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag9.symbols @@ -0,0 +1,54 @@ +//// [tests/cases/conformance/jsdoc/extendsTag9.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : Symbol(A, Decl(a.js, 0, 0)) + + /** @returns {T} */ + get a() { +>a : Symbol(A.a, Decl(a.js, 1, 9)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } + + /** @returns {typeof C} */ + static extend() { +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + + return C; +>C : Symbol(C, Decl(a.js, 11, 1)) + } +} + +/** @template T */ +class C { +>C : Symbol(C, Decl(a.js, 11, 1)) + + /** @returns {T} */ + get c() { +>c : Symbol(C.c, Decl(a.js, 14, 9)) + + throw new Error(); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : Symbol(B, Decl(a.js, 19, 1)) +>A.extend : Symbol(A.extend, Decl(a.js, 5, 5)) +>A : Symbol(A, Decl(a.js, 0, 0)) +>extend : Symbol(A.extend, Decl(a.js, 5, 5)) + +const a = new B().a; +>a : Symbol(a, Decl(a.js, 24, 5)) +>B : Symbol(B, Decl(a.js, 19, 1)) + +const c = new B().c; +>c : Symbol(c, Decl(a.js, 25, 5)) +>new B().c : Symbol(C.c, Decl(a.js, 14, 9)) +>B : Symbol(B, Decl(a.js, 19, 1)) +>c : Symbol(C.c, Decl(a.js, 14, 9)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag9.types b/tsc/testdata/baselines/reference/conformance/extendsTag9.types new file mode 100644 index 0000000000000..42d02b953dc31 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag9.types @@ -0,0 +1,61 @@ +//// [tests/cases/conformance/jsdoc/extendsTag9.ts] //// + +=== a.js === +/** @template T */ +class A { +>A : A + + /** @returns {T} */ + get a() { +>a : T + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } + + /** @returns {typeof C} */ + static extend() { +>extend : () => typeof C + + return C; +>C : typeof C + } +} + +/** @template T */ +class C { +>C : C + + /** @returns {T} */ + get c() { +>c : T + + throw new Error(); +>new Error() : Error +>Error : ErrorConstructor + } +} + +/** @extends {A} */ +class B extends A.extend() {} +>B : B +>A.extend() : C +>A.extend : () => typeof C +>A : typeof A +>extend : () => typeof C + +const a = new B().a; +>a : any +>new B().a : any +>new B() : B +>B : typeof B +>a : any + +const c = new B().c; +>c : string +>new B().c : string +>new B() : B +>B : typeof B +>c : string + diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag10.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag10.ts new file mode 100644 index 0000000000000..93c7e3e57b711 --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag10.ts @@ -0,0 +1,27 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @declaration: true +// @outDir: out + +// @filename: a.js +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } +} + +/** @param {any} Base */ +function mixin(Base) { + return class extends Base { + extra = 1; + }; +} + +/** @extends {A} */ +class B extends mixin(A) {} + +const value = new B().value; +const extra = new B().extra; diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag11.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag11.ts new file mode 100644 index 0000000000000..2d7b0d7779701 --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag11.ts @@ -0,0 +1,42 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: a.js +/** @template T */ +class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } + + /** @returns {typeof A} */ + static extend() { + return this; + } +} + +/** @extends {A} */ +class B extends A.extend() {} + +new B("ok"); +new B(1); + +/** @extends {A} */ +class C extends A.extend() { + constructor() { + super(1); + } +} + +/** + * @param {number} required + * @returns {typeof A} + */ +function getA(required) { + return A; +} + +/** @extends {A} */ +class D extends getA("wrong") {} diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag12.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag12.ts new file mode 100644 index 0000000000000..c6c96aef8f4de --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag12.ts @@ -0,0 +1,72 @@ +// @target: esnext +// @allowJs: true +// @checkJs: true +// @strict: true +// @noEmit: true + +// @filename: a.js +/** @template T */ +class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } +} + +/** + * @template T + * @extends {A} + */ +class Sub extends A { + /** @returns {typeof Sub} */ + static extend() { + return this; + } +} + +/** @template T */ +class Other { + /** @param {T} other */ + constructor(other) { + this.other = other; + } +} + +/** + * @template {new (...args: any[]) => any} C + * @param {C} Base + */ +function mixin(Base) { + return class extends Base { + extra = 1; + }; +} + +const ns = { A }; +/** @type {boolean} */ +let cond = true; + +/** @extends {A} */ +class ViaMixin extends mixin(A) {} +/** @type {string} */ +const viaMixin = new ViaMixin("ok").value; +/** @type {number} */ +const viaMixinExtra = new ViaMixin("ok").extra; + +/** @extends {Other} */ +class MismatchedMixin extends mixin(A) {} + +/** @extends {A} */ +class ViaSubclass extends Sub.extend() {} +/** @type {string} */ +const viaSubclass = new ViaSubclass("ok").value; + +/** @extends {A} */ +class ViaElementAccess extends ns["A"] {} +/** @type {string} */ +const viaElementAccess = new ViaElementAccess("ok").value; + +/** @extends {A} */ +class ViaConditional extends (cond ? A : A) {} +/** @type {string} */ +const viaConditional = new ViaConditional("ok").value; diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag13.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag13.ts new file mode 100644 index 0000000000000..9b8e0cbacbb7f --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag13.ts @@ -0,0 +1,65 @@ +// @target: esnext +// @allowJs: true +// @checkJs: true +// @strict: true +// @noEmit: true + +// @filename: a.js +/** @template T */ +class A { + /** @param {T} value */ + constructor(value) { + this.value = value; + } + + /** @returns {typeof A} */ + static extend() { + return this; + } +} + +/** @template T */ +class Other { + /** @param {T} other */ + constructor(other) { + this.other = other; + } +} + +const ns = { A }; + +/** + * @extends {A} + * @extends {Other} + */ +class Direct extends A {} + +/** + * @extends {A} + * @extends {Other} + * @augments {Other} + */ +class Call extends A.extend() {} + +/** @extends {A} */ +/** @extends {Other} */ +/** @extends {A} */ +class SeparateDirect extends A {} + +/** @extends {A} */ +/** @augments {Other} */ +class SeparateElement extends ns["A"] {} + +/** + * @extends {Other} + * @extends {A} + */ +class FirstMismatch extends A.extend() {} + +/** + * @extends {A} + * @augments {A} + */ +class CompatibleTags extends A.extend() {} +/** @type {string} */ +const value = new CompatibleTags("ok").value; diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag7.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag7.ts new file mode 100644 index 0000000000000..3733efd21dbce --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag7.ts @@ -0,0 +1,24 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @declaration: true +// @outDir: out + +// @filename: a.js +/** @template T */ +class A { + /** @returns {T} */ + get value() { + throw new Error(); + } + + /** @returns {typeof A} */ + static extend() { + return this; + } +} + +/** @extends {A} */ +class B extends A.extend() {} + +const value = new B().value; diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag8.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag8.ts new file mode 100644 index 0000000000000..d63c39d8f257c --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag8.ts @@ -0,0 +1,19 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: a.js +/** @template T */ +class A { + /** @returns {typeof A} */ + static extend() { + return this; + } +} + +/** @template T */ +class C {} + +/** @extends {C} */ +class B extends A.extend() {} diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag9.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag9.ts new file mode 100644 index 0000000000000..a5fe80b7e5fde --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag9.ts @@ -0,0 +1,32 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @filename: a.js +/** @template T */ +class A { + /** @returns {T} */ + get a() { + throw new Error(); + } + + /** @returns {typeof C} */ + static extend() { + return C; + } +} + +/** @template T */ +class C { + /** @returns {T} */ + get c() { + throw new Error(); + } +} + +/** @extends {A} */ +class B extends A.extend() {} + +const a = new B().a; +const c = new B().c; From 870635e603b597c220d11d85e26719e7e21ce152 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Wed, 14 Oct 2026 18:32:16 +0300 Subject: [PATCH 2/2] skip jsdoc heritage type arguments for extends null --- tsc/internal/parser/parser_test.go | 1 + tsc/internal/parser/reparser.go | 2 +- .../reference/conformance/extendsTag14.js | 36 +++++++++++++++++++ .../conformance/extendsTag14.symbols | 15 ++++++++ .../reference/conformance/extendsTag14.types | 15 ++++++++ .../cases/conformance/jsdoc/extendsTag14.ts | 15 ++++++++ 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag14.js create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag14.symbols create mode 100644 tsc/testdata/baselines/reference/conformance/extendsTag14.types create mode 100644 tsc/testdata/tests/cases/conformance/jsdoc/extendsTag14.ts diff --git a/tsc/internal/parser/parser_test.go b/tsc/internal/parser/parser_test.go index 27082afcb57b3..ffa96713eb444 100644 --- a/tsc/internal/parser/parser_test.go +++ b/tsc/internal/parser/parser_test.go @@ -502,6 +502,7 @@ func TestJSDocAugmentsHeritage(t *testing.T) { {"element access", `ns["A"]`, "A", true}, {"conditional", "(cond ? A : A)", "A", true}, {"parenthesized name", "(A)", "A", true}, + {"null", "null", "A", false}, {"mismatched identifier", "A", "Other", false}, {"mismatched dotted name", "ns.A", "ns.Other", false}, } { diff --git a/tsc/internal/parser/reparser.go b/tsc/internal/parser/reparser.go index 418f48210488d..bd0e5e4d04588 100644 --- a/tsc/internal/parser/reparser.go +++ b/tsc/internal/parser/reparser.go @@ -597,7 +597,7 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node) }); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 { target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments() source := tag.ClassName().AsExpressionWithTypeArguments() - if target.TypeArguments == nil && source.TypeArguments != nil && (!ast.IsEntityNameExpression(target.Expression) || ast.HasSamePropertyAccessName(target.Expression, source.Expression)) { + if target.TypeArguments == nil && source.TypeArguments != nil && target.Expression.Kind != ast.KindNullKeyword && (!ast.IsEntityNameExpression(target.Expression) || ast.HasSamePropertyAccessName(target.Expression, source.Expression)) { p.setReparsedTypeArguments(target, source.TypeArguments) } } diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag14.js b/tsc/testdata/baselines/reference/conformance/extendsTag14.js new file mode 100644 index 0000000000000..e1d6e96bb50ef --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag14.js @@ -0,0 +1,36 @@ +//// [tests/cases/conformance/jsdoc/extendsTag14.ts] //// + +//// [a.js] +/** @template T */ +class A {} + +/** @extends {A} */ +class B extends null {} + +/** @augments {A} */ +class C extends null {} + + +//// [a.js] +"use strict"; +/** @template T */ +class A { +} +/** @extends {A} */ +class B extends null { +} +/** @augments {A} */ +class C extends null { +} + + +//// [a.d.ts] +/** @template T */ +declare class A { +} +/** @extends {A} */ +declare class B extends null { +} +/** @augments {A} */ +declare class C extends null { +} diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag14.symbols b/tsc/testdata/baselines/reference/conformance/extendsTag14.symbols new file mode 100644 index 0000000000000..9ef7f552edcac --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag14.symbols @@ -0,0 +1,15 @@ +//// [tests/cases/conformance/jsdoc/extendsTag14.ts] //// + +=== a.js === +/** @template T */ +class A {} +>A : Symbol(A, Decl(a.js, 0, 0)) + +/** @extends {A} */ +class B extends null {} +>B : Symbol(B, Decl(a.js, 1, 10)) + +/** @augments {A} */ +class C extends null {} +>C : Symbol(C, Decl(a.js, 4, 23)) + diff --git a/tsc/testdata/baselines/reference/conformance/extendsTag14.types b/tsc/testdata/baselines/reference/conformance/extendsTag14.types new file mode 100644 index 0000000000000..ae69251410959 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/extendsTag14.types @@ -0,0 +1,15 @@ +//// [tests/cases/conformance/jsdoc/extendsTag14.ts] //// + +=== a.js === +/** @template T */ +class A {} +>A : A + +/** @extends {A} */ +class B extends null {} +>B : B + +/** @augments {A} */ +class C extends null {} +>C : C + diff --git a/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag14.ts b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag14.ts new file mode 100644 index 0000000000000..b3a5217e96d07 --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/jsdoc/extendsTag14.ts @@ -0,0 +1,15 @@ +// @target: es2015 +// @allowJs: true +// @checkJs: true +// @declaration: true +// @outDir: out + +// @filename: a.js +/** @template T */ +class A {} + +/** @extends {A} */ +class B extends null {} + +/** @augments {A} */ +class C extends null {}