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
13 changes: 11 additions & 2 deletions tsc/internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -4417,11 +4417,20 @@ func (c *Checker) checkJSDocAugmentsTagMatchesExtends(node *ast.Node, baseTypeNo
continue
}
sourceTypeNode := tag.ClassName()
if c.isTypeIdenticalTo(c.getTypeFromTypeNode(sourceTypeNode), baseType) {
sourceType := c.getTypeFromTypeNode(sourceTypeNode)
sourceName := getIdentifierFromEntityNameExpression(sourceTypeNode.Expression())
if sourceName != nil && ast.IsCallExpression(ast.SkipParentheses(baseTypeNode.Expression())) {
sourceSymbol := c.getMergedSymbol(getTargetType(sourceType).symbol)
targetSymbol := c.getMergedSymbol(getTargetType(baseType).symbol)
if sourceSymbol != nil && targetSymbol != nil && sourceSymbol != targetSymbol {
c.error(sourceName, diagnostics.JSDoc_0_1_does_not_match_the_extends_2_clause, tag.TagName().Text(), sourceName.Text(), targetSymbol.Name)
continue
}
}
if c.isTypeIdenticalTo(sourceType, 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())
}
Expand Down
50 changes: 50 additions & 0 deletions tsc/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,56 @@ function foo(options) {}`
assert.Equal(t, scanner.GetTokenPosOfNode(typeNode, file, false /*includeJSDoc*/), strings.Index(sourceText, "{{")+1)
}

func TestJSDocAugmentsCallHeritage(t *testing.T) {
t.Parallel()
sourceText := `/** @template T */
class A {
static extend() {
return this;
}
}

/** @extends {A<string>} */
class B extends A.extend() {}`
opts := ast.SourceFileParseOptions{
FileName: "/index.js",
Path: "/index.js",
}

file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS)
statements := file.Statements.Nodes
assert.Equal(t, len(statements), 2)

classB := statements[1]
assert.Assert(t, ast.IsClassDeclaration(classB))

baseType := ast.GetClassExtendsHeritageElement(classB)
assert.Assert(t, baseType != nil)
assert.Assert(t, ast.IsCallExpression(baseType.Expression()))
assert.Equal(t, scanner.GetTextOfNode(baseType.Expression()), "A.extend()")

typeArguments := baseType.TypeArguments()
assert.Equal(t, len(typeArguments), 1)

typeArgument := typeArguments[0]
assert.Equal(t, typeArgument.Kind, ast.KindStringKeyword)
assert.Assert(t, typeArgument.Flags&ast.NodeFlagsReparsed != 0)

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)
assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), typeArgument)
}

func TestSourceFilePositionMapWithNonASCIIStringLiteral(t *testing.T) {
t.Parallel()
sourceText := `const x = "─";
Expand Down
20 changes: 12 additions & 8 deletions tsc/internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,21 +597,25 @@ 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 {
if ast.IsCallExpression(ast.SkipParentheses(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 {
Expand Down
54 changes: 54 additions & 0 deletions tsc/testdata/baselines/reference/conformance/extendsTag7.js
Original file line number Diff line number Diff line change
@@ -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<string>} */
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<string>} */
class B extends A.extend() {
}
const value = new B().value;


//// [a.d.ts]
/** @template T */
declare class A<T> {
/** @returns {T} */
get value(): T;
/** @returns {typeof A} */
static extend(): typeof A;
}
declare const B_base: typeof A;
/** @extends {A<string>} */
declare class B extends B_base<string> {
}
declare const value: string;
37 changes: 37 additions & 0 deletions tsc/testdata/baselines/reference/conformance/extendsTag7.symbols
Original file line number Diff line number Diff line change
@@ -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<string>} */
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))

40 changes: 40 additions & 0 deletions tsc/testdata/baselines/reference/conformance/extendsTag7.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [tests/cases/conformance/jsdoc/extendsTag7.ts] ////

=== a.js ===
/** @template T */
class A {
>A : A<T>

/** @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<string>} */
class B extends A.extend() {}
>B : B
>A.extend() : A<string>
>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

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
a.js(12,15): error TS8023: JSDoc '@extends C' does not match the 'extends A' clause.


==== a.js (1 errors) ====
/** @template T */
class A {
/** @returns {typeof A} */
static extend() {
return this;
}
}

/** @template T */
class C {}

/** @extends {C<string>} */
~
!!! error TS8023: JSDoc '@extends C' does not match the 'extends A' clause.
class B extends A.extend() {}

27 changes: 27 additions & 0 deletions tsc/testdata/baselines/reference/conformance/extendsTag8.symbols
Original file line number Diff line number Diff line change
@@ -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<string>} */
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))

28 changes: 28 additions & 0 deletions tsc/testdata/baselines/reference/conformance/extendsTag8.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/conformance/jsdoc/extendsTag8.ts] ////

=== a.js ===
/** @template T */
class A {
>A : A<T>

/** @returns {typeof A} */
static extend() {
>extend : () => typeof A

return this;
>this : typeof A
}
}

/** @template T */
class C {}
>C : C<T>

/** @extends {C<string>} */
class B extends A.extend() {}
>B : B
>A.extend() : A<string>
>A.extend : () => typeof A
>A : typeof A
>extend : () => typeof A

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
a.js(22,15): error TS8023: JSDoc '@extends A' does not match the 'extends C' 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<string>} */
~
!!! error TS8023: JSDoc '@extends A' does not match the 'extends C' 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;

Loading