Skip to content

Commit f199fb7

Browse files
committed
fix(64058): fix reparse jsdoc @extends type arguments for call expressions
1 parent e26b8d2 commit f199fb7

6 files changed

Lines changed: 155 additions & 18 deletions

File tree

‎tsc/internal/ast/utilities.go‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,16 +1645,6 @@ func IsDottedName(node *Node) bool {
16451645
return false
16461646
}
16471647

1648-
func HasSamePropertyAccessName(node1, node2 *Node) bool {
1649-
if node1.Kind == KindIdentifier && node2.Kind == KindIdentifier {
1650-
return node1.Text() == node2.Text()
1651-
} else if node1.Kind == KindPropertyAccessExpression && node2.Kind == KindPropertyAccessExpression {
1652-
return node1.AsPropertyAccessExpression().Name().Text() == node2.AsPropertyAccessExpression().Name().Text() &&
1653-
HasSamePropertyAccessName(node1.Expression(), node2.Expression())
1654-
}
1655-
return false
1656-
}
1657-
16581648
func IsAmbientModule(node *Node) bool {
16591649
return IsModuleDeclaration(node) && (node.AsModuleDeclaration().Name().Kind == KindStringLiteral || IsGlobalScopeAugmentation(node))
16601650
}

‎tsc/internal/parser/parser_test.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,55 @@ function foo(options) {}`
308308
assert.Equal(t, scanner.GetTokenPosOfNode(typeNode, file, false /*includeJSDoc*/), strings.Index(sourceText, "{{")+1)
309309
}
310310

311+
func TestJSDocAugmentsTypeArgumentsReparse(t *testing.T) {
312+
t.Parallel()
313+
sourceText := `/** @template T */
314+
class A {
315+
static extend() {
316+
return this;
317+
}
318+
}
319+
320+
/** @extends {A<string>} */
321+
class B extends A.extend() {}`
322+
opts := ast.SourceFileParseOptions{
323+
FileName: "/index.js",
324+
Path: "/index.js",
325+
}
326+
327+
file := parser.ParseSourceFile(opts, sourceText, core.ScriptKindJS)
328+
statements := file.Statements.Nodes
329+
assert.Equal(t, len(statements), 2)
330+
331+
classB := statements[1]
332+
assert.Assert(t, ast.IsClassDeclaration(classB))
333+
334+
baseType := ast.GetClassExtendsHeritageElement(classB)
335+
assert.Assert(t, baseType != nil)
336+
assert.Assert(t, ast.IsCallExpression(baseType.Expression()))
337+
338+
typeArguments := baseType.TypeArguments()
339+
assert.Equal(t, len(typeArguments), 1)
340+
341+
typeArgument := typeArguments[0]
342+
assert.Equal(t, typeArgument.Kind, ast.KindStringKeyword)
343+
assert.Assert(t, typeArgument.Flags&ast.NodeFlagsReparsed != 0)
344+
345+
jsDocs := classB.JSDoc(file)
346+
assert.Equal(t, len(jsDocs), 1)
347+
348+
tags := jsDocs[0].AsJSDoc().Tags
349+
assert.Assert(t, tags != nil)
350+
assert.Equal(t, len(tags.Nodes), 1)
351+
352+
tag := tags.Nodes[0]
353+
assert.Assert(t, ast.IsJSDocAugmentsTag(tag))
354+
355+
sourceTypeArguments := tag.ClassName().TypeArguments()
356+
assert.Equal(t, len(sourceTypeArguments), 1)
357+
assert.Equal(t, ast.GetReparsedNodeForNode(sourceTypeArguments[0]), typeArgument)
358+
}
359+
311360
func TestSourceFilePositionMapWithNonASCIIStringLiteral(t *testing.T) {
312361
t.Parallel()
313362
sourceText := `const x = "─";

‎tsc/internal/parser/reparser.go‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -597,15 +597,13 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
597597
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
598598
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
599599
source := tag.ClassName().AsExpressionWithTypeArguments()
600-
if ast.HasSamePropertyAccessName(target.Expression, source.Expression) {
601-
if target.TypeArguments == nil && source.TypeArguments != nil {
602-
newArguments := p.nodeSliceArena.NewSlice(len(source.TypeArguments.Nodes))
603-
for i, arg := range source.TypeArguments.Nodes {
604-
newArguments[i] = p.addDeepCloneReparse(arg)
605-
}
606-
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
607-
p.finishMutatedNode(target.AsNode())
600+
if target.TypeArguments == nil && source.TypeArguments != nil {
601+
newArguments := p.nodeSliceArena.NewSlice(len(source.TypeArguments.Nodes))
602+
for i, arg := range source.TypeArguments.Nodes {
603+
newArguments[i] = p.addDeepCloneReparse(arg)
608604
}
605+
target.TypeArguments = p.newNodeList(source.TypeArguments.Loc, newArguments)
606+
p.finishMutatedNode(target.AsNode())
609607
}
610608
}
611609
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [tests/cases/conformance/jsdoc/extendsTag7.ts] ////
2+
3+
=== a.js ===
4+
/** @template T */
5+
class A {
6+
>A : Symbol(A, Decl(a.js, 0, 0))
7+
8+
/** @returns {T} */
9+
get value() {
10+
>value : Symbol(A.value, Decl(a.js, 1, 9))
11+
12+
throw new Error();
13+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
14+
}
15+
16+
/** @returns {typeof A} */
17+
static extend() {
18+
>extend : Symbol(A.extend, Decl(a.js, 5, 5))
19+
20+
return this;
21+
>this : Symbol(A, Decl(a.js, 0, 0))
22+
}
23+
}
24+
25+
/** @extends {A<string>} */
26+
class B extends A.extend() {}
27+
>B : Symbol(B, Decl(a.js, 11, 1))
28+
>A.extend : Symbol(A.extend, Decl(a.js, 5, 5))
29+
>A : Symbol(A, Decl(a.js, 0, 0))
30+
>extend : Symbol(A.extend, Decl(a.js, 5, 5))
31+
32+
const value = new B().value;
33+
>value : Symbol(value, Decl(a.js, 16, 5))
34+
>new B().value : Symbol(A.value, Decl(a.js, 1, 9))
35+
>B : Symbol(B, Decl(a.js, 11, 1))
36+
>value : Symbol(A.value, Decl(a.js, 1, 9))
37+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [tests/cases/conformance/jsdoc/extendsTag7.ts] ////
2+
3+
=== a.js ===
4+
/** @template T */
5+
class A {
6+
>A : A<T>
7+
8+
/** @returns {T} */
9+
get value() {
10+
>value : T
11+
12+
throw new Error();
13+
>new Error() : Error
14+
>Error : ErrorConstructor
15+
}
16+
17+
/** @returns {typeof A} */
18+
static extend() {
19+
>extend : () => typeof A
20+
21+
return this;
22+
>this : typeof A
23+
}
24+
}
25+
26+
/** @extends {A<string>} */
27+
class B extends A.extend() {}
28+
>B : B
29+
>A.extend() : A<string>
30+
>A.extend : () => typeof A
31+
>A : typeof A
32+
>extend : () => typeof A
33+
34+
const value = new B().value;
35+
>value : string
36+
>new B().value : string
37+
>new B() : B
38+
>B : typeof B
39+
>value : string
40+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @target: es2015
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @noEmit: true
5+
6+
// @filename: a.js
7+
/** @template T */
8+
class A {
9+
/** @returns {T} */
10+
get value() {
11+
throw new Error();
12+
}
13+
14+
/** @returns {typeof A} */
15+
static extend() {
16+
return this;
17+
}
18+
}
19+
20+
/** @extends {A<string>} */
21+
class B extends A.extend() {}
22+
23+
const value = new B().value;

0 commit comments

Comments
 (0)