Skip to content

Commit c202a92

Browse files
authored
Add @implements/@extends tags (#1111)
1 parent d9b5a75 commit c202a92

33 files changed

+373
-787
lines changed

internal/parser/reparser.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,55 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
271271
fun.FunctionLikeData().Type = p.makeNewType(tag.AsJSDocReturnTag().TypeExpression, fun)
272272
}
273273
}
274+
case ast.KindJSDocImplementsTag:
275+
if class := getClassLikeData(parent); class != nil {
276+
implementsTag := tag.AsJSDocImplementsTag()
277+
278+
if class.HeritageClauses != nil {
279+
if implementsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
280+
return node.AsHeritageClause().Token == ast.KindImplementsKeyword
281+
}); implementsClause != nil {
282+
implementsClause.AsHeritageClause().Types.Nodes = append(implementsClause.AsHeritageClause().Types.Nodes, implementsTag.ClassName)
283+
return
284+
}
285+
}
286+
types := p.nodeSlicePool.NewSlice(1)
287+
types[0] = implementsTag.ClassName
288+
implementsTag.ClassName.Flags |= ast.NodeFlagsReparsed
289+
typesList := p.newNodeList(implementsTag.ClassName.Loc, types)
290+
291+
heritageClause := p.factory.NewHeritageClause(ast.KindImplementsKeyword, typesList)
292+
heritageClause.Loc = implementsTag.ClassName.Loc
293+
heritageClause.Flags = p.contextFlags | ast.NodeFlagsReparsed
294+
295+
if class.HeritageClauses == nil {
296+
heritageClauses := p.newNodeList(implementsTag.ClassName.Loc, p.nodeSlicePool.NewSlice(1))
297+
heritageClauses.Nodes[0] = heritageClause
298+
class.HeritageClauses = heritageClauses
299+
} else {
300+
class.HeritageClauses.Nodes = append(class.HeritageClauses.Nodes, heritageClause)
301+
}
302+
}
303+
case ast.KindJSDocAugmentsTag:
304+
if class := getClassLikeData(parent); class != nil && class.HeritageClauses != nil {
305+
if extendsClause := core.Find(class.HeritageClauses.Nodes, func(node *ast.Node) bool {
306+
return node.AsHeritageClause().Token == ast.KindExtendsKeyword
307+
}); extendsClause != nil && len(extendsClause.AsHeritageClause().Types.Nodes) == 1 {
308+
target := extendsClause.AsHeritageClause().Types.Nodes[0].AsExpressionWithTypeArguments()
309+
source := tag.AsJSDocAugmentsTag().ClassName.AsExpressionWithTypeArguments()
310+
if hasSamePropertyAccessName(target.Expression, source.Expression) {
311+
if target.TypeArguments == nil && source.TypeArguments != nil {
312+
target.TypeArguments = source.TypeArguments
313+
for _, typeArg := range source.TypeArguments.Nodes {
314+
typeArg.Flags |= ast.NodeFlagsReparsed
315+
}
316+
}
317+
return
318+
}
319+
}
320+
}
274321
}
322+
// !!! other attached tags (@this, @satisfies) support goes here
275323
}
276324

277325
func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterTag) *ast.Node {
@@ -338,3 +386,23 @@ func (p *Parser) makeNewType(typeExpression *ast.TypeNode, host *ast.Node) *ast.
338386
t.Flags |= ast.NodeFlagsReparsed
339387
return t
340388
}
389+
390+
func hasSamePropertyAccessName(node1, node2 *ast.Node) bool {
391+
if node1.Kind == ast.KindIdentifier && node2.Kind == ast.KindIdentifier {
392+
return node1.Text() == node2.Text()
393+
} else if node1.Kind == ast.KindPropertyAccessExpression && node2.Kind == ast.KindPropertyAccessExpression {
394+
return node1.AsPropertyAccessExpression().Name().Text() == node2.AsPropertyAccessExpression().Name().Text() &&
395+
hasSamePropertyAccessName(node1.AsPropertyAccessExpression().Expression, node2.AsPropertyAccessExpression().Expression)
396+
}
397+
return false
398+
}
399+
400+
func getClassLikeData(parent *ast.Node) *ast.ClassLikeBase {
401+
var class *ast.ClassLikeBase
402+
if parent.Kind == ast.KindClassDeclaration {
403+
class = parent.AsClassDeclaration().ClassLikeData()
404+
} else if parent.Kind == ast.KindClassExpression {
405+
class = parent.AsClassExpression().ClassLikeData()
406+
}
407+
return class
408+
}

testdata/baselines/reference/submodule/conformance/extendsTag1.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
*/
88
class My extends Set {}
99
>My : My<T>
10-
>Set : Set<any>
10+
>Set : Set<T>
1111

testdata/baselines/reference/submodule/conformance/extendsTag5.errors.txt

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
/a.js(26,17): error TS2314: Generic type 'A<T>' requires 1 type argument(s).
2-
/a.js(34,17): error TS2314: Generic type 'A<T>' requires 1 type argument(s).
3-
/a.js(39,17): error TS2314: Generic type 'A<T>' requires 1 type argument(s).
4-
/a.js(44,17): error TS2314: Generic type 'A<T>' requires 1 type argument(s).
1+
/a.js(29,16): error TS2344: Type '{ a: string; b: string; }' does not satisfy the constraint '{ a: string | number; b: boolean | string[]; }'.
2+
Types of property 'b' are incompatible.
3+
Type 'string' is not assignable to type 'boolean | string[]'.
4+
/a.js(42,16): error TS2344: Type '{ a: string; b: string; }' does not satisfy the constraint '{ a: string | number; b: boolean | string[]; }'.
5+
Types of property 'b' are incompatible.
6+
Type 'string' is not assignable to type 'boolean | string[]'.
57

68

7-
==== /a.js (4 errors) ====
9+
==== /a.js (2 errors) ====
810
/**
911
* @typedef {{
1012
* a: number | string;
@@ -31,30 +33,33 @@
3133
* }>}
3234
*/
3335
class B extends A {}
34-
~
35-
!!! error TS2314: Generic type 'A<T>' requires 1 type argument(s).
3636

3737
/**
3838
* @extends {A<{
39+
~
3940
* a: string,
41+
~~~~~~~~~~~~~~~~~
4042
* b: string
43+
~~~~~~~~~~~~~~~~
4144
* }>}
45+
~~~~
46+
!!! error TS2344: Type '{ a: string; b: string; }' does not satisfy the constraint '{ a: string | number; b: boolean | string[]; }'.
47+
!!! error TS2344: Types of property 'b' are incompatible.
48+
!!! error TS2344: Type 'string' is not assignable to type 'boolean | string[]'.
4249
*/
4350
class C extends A {}
44-
~
45-
!!! error TS2314: Generic type 'A<T>' requires 1 type argument(s).
4651

4752
/**
4853
* @extends {A<{a: string, b: string[]}>}
4954
*/
5055
class D extends A {}
51-
~
52-
!!! error TS2314: Generic type 'A<T>' requires 1 type argument(s).
5356

5457
/**
5558
* @extends {A<{a: string, b: string}>}
59+
~~~~~~~~~~~~~~~~~~~~~~
60+
!!! error TS2344: Type '{ a: string; b: string; }' does not satisfy the constraint '{ a: string | number; b: boolean | string[]; }'.
61+
!!! error TS2344: Types of property 'b' are incompatible.
62+
!!! error TS2344: Type 'string' is not assignable to type 'boolean | string[]'.
5663
*/
5764
class E extends A {}
58-
~
59-
!!! error TS2314: Generic type 'A<T>' requires 1 type argument(s).
6065

testdata/baselines/reference/submodule/conformance/extendsTag5.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class A {
3333
*/
3434
class B extends A {}
3535
>B : B
36-
>A : typeof A
36+
>A : A<{ a: string; b: string[]; }>
3737

3838
/**
3939
* @extends {A<{
@@ -43,19 +43,19 @@ class B extends A {}
4343
*/
4444
class C extends A {}
4545
>C : C
46-
>A : typeof A
46+
>A : A<{ a: string; b: string; }>
4747

4848
/**
4949
* @extends {A<{a: string, b: string[]}>}
5050
*/
5151
class D extends A {}
5252
>D : D
53-
>A : typeof A
53+
>A : A<{ a: string; b: string[]; }>
5454

5555
/**
5656
* @extends {A<{a: string, b: string}>}
5757
*/
5858
class E extends A {}
5959
>E : E
60-
>A : typeof A
60+
>A : A<{ a: string; b: string; }>
6161

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/b.js(5,18): error TS1361: 'NS' cannot be used as a value because it was imported using 'import type'.
2+
/b.js(6,14): error TS2420: Class 'C' incorrectly implements interface 'I'.
3+
Property 'foo' is missing in type 'C' but required in type 'I'.
4+
5+
6+
==== /a.ts (0 errors) ====
7+
export interface I {
8+
foo(): void;
9+
}
10+
11+
==== /b.js (2 errors) ====
12+
/**
13+
* @import * as NS from './a'
14+
*/
15+
16+
/** @implements {NS.I} */
17+
~~
18+
!!! error TS1361: 'NS' cannot be used as a value because it was imported using 'import type'.
19+
!!! related TS1376 /b.js:2:17: 'NS' was imported here.
20+
export class C {}
21+
~
22+
!!! error TS2420: Class 'C' incorrectly implements interface 'I'.
23+
!!! error TS2420: Property 'foo' is missing in type 'C' but required in type 'I'.
24+
!!! related TS2728 /a.ts:2:5: 'foo' is declared here.
25+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
lib.js(3,17): error TS2552: Cannot find name 'IEncoder'. Did you mean 'Encoder'?
2+
3+
4+
==== interface.ts (0 errors) ====
5+
export interface Encoder<T> {
6+
encode(value: T): Uint8Array
7+
}
8+
==== lib.js (1 errors) ====
9+
/**
10+
* @template T
11+
* @implements {IEncoder<T>}
12+
~~~~~~~~
13+
!!! error TS2552: Cannot find name 'IEncoder'. Did you mean 'Encoder'?
14+
!!! related TS2728 lib.js:5:14: 'Encoder' is declared here.
15+
*/
16+
export class Encoder {
17+
/**
18+
* @param {T} value
19+
*/
20+
encode(value) {
21+
return new Uint8Array(0)
22+
}
23+
}
24+
25+
26+
/**
27+
* @template T
28+
* @typedef {import('./interface').Encoder<T>} IEncoder
29+
*/

0 commit comments

Comments
 (0)