Skip to content

Commit 1f19a57

Browse files
authored
Revise logic for gathering JSDoc @template type parameters (#4065)
1 parent 82d34b6 commit 1f19a57

27 files changed

Lines changed: 333 additions & 280 deletions

CHANGES.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,42 @@ function f(cu) {
273273
In Strada, `cu` incorrectly narrows to `C` inside the `if` block, unlike with TS assertion syntax.
274274
In Corsa, the behaviour is the same between TS and JS.
275275

276+
#### `@overload` with arrow functions and function expressions
277+
278+
In Strada, `@overload` can be used in JSDoc annotations for arrow functions and function expressions. Corsa more closely aligns with TypeScript by internally translating JSDoc constructs into synthetic TypeScript constructs which are then checked. However, since TypeScript itself currently doesn't support overload declarations with arrow function and function expressions, Corsa ignores `@overload` annotations on those constructs. Instead of writing:
279+
280+
```js
281+
/**
282+
* @overload
283+
* @param {string} x
284+
* @returns {string}
285+
*
286+
* @overload
287+
* @param {number} x
288+
* @returns {number}
289+
*
290+
* @param {string | number} x
291+
* @returns {string | number}
292+
*/
293+
let f = x => x;
294+
```
295+
296+
You should write:
297+
298+
```js
299+
/**
300+
* @type {{
301+
* (x: string): string;
302+
* (x: number): number;
303+
* }}
304+
* @param {string | number} x
305+
* @returns {any}
306+
*/
307+
const f = x => x;
308+
```
309+
310+
This works with both TS6 and TS7. Note the change to `any` for the return type annotation. This is to satisfy the assignment check.
311+
276312
### Expandos
277313

278314
#### Constructor functions are no longer supported

internal/parser/jsdoc.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -865,9 +865,10 @@ func (p *Parser) parseNestedTypeLiteral(typeExpression *ast.Node, name *ast.Enti
865865
p.rewind(state)
866866
break
867867
}
868-
if child.Kind == ast.KindJSDocParameterTag || child.Kind == ast.KindJSDocPropertyTag {
868+
switch child.Kind {
869+
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
869870
children = append(children, child)
870-
} else if child.Kind == ast.KindJSDocTemplateTag {
871+
case ast.KindJSDocTemplateTag:
871872
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
872873
}
873874
}
@@ -1035,11 +1036,11 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent
10351036
p.rewind(state)
10361037
break
10371038
}
1038-
if child.Kind == ast.KindJSDocTemplateTag {
1039-
break
1040-
}
10411039
hasChildren = true
1042-
if child.Kind == ast.KindJSDocTypeTag {
1040+
switch child.Kind {
1041+
case ast.KindJSDocTemplateTag:
1042+
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
1043+
case ast.KindJSDocTypeTag:
10431044
if childTypeTag == nil {
10441045
childTypeTag = child.AsJSDocTypeTag()
10451046
} else {
@@ -1048,9 +1049,8 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent
10481049
related := ast.NewDiagnostic(nil, core.NewTextRange(0, 0), diagnostics.The_tag_was_first_specified_here)
10491050
lastError.AddRelatedInfo(related)
10501051
}
1051-
break
10521052
}
1053-
} else {
1053+
default:
10541054
jsdocPropertyTags = append(jsdocPropertyTags, child)
10551055
}
10561056
}
@@ -1110,9 +1110,9 @@ func (p *Parser) parseCallbackTagParameters(indent int) *ast.NodeList {
11101110
}
11111111
if child.Kind == ast.KindJSDocTemplateTag {
11121112
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
1113-
break
1113+
} else {
1114+
parameters = append(parameters, child)
11141115
}
1115-
parameters = append(parameters, child)
11161116
}
11171117
return p.newNodeList(core.NewTextRange(pos, p.nodePos()), parameters)
11181118
}

internal/parser/reparser.go

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
5858
modifiers = p.createExportModifier(tag)
5959
}
6060
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.getInnermostNameOfJSDocNamespace(fullName)), nil, nil)
61-
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
61+
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
6262
var t *ast.Node
6363
switch typeExpression.Kind {
6464
case ast.KindJSDocTypeExpression:
@@ -87,7 +87,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
8787
}
8888
functionType := p.reparseJSDocSignature(typeExpression, tag, jsDoc, tag, nil)
8989
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.getInnermostNameOfJSDocNamespace(fullName)), nil, functionType)
90-
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
90+
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
9191
p.finishReparsedNode(typeAlias, tag)
9292
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
9393
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
@@ -133,7 +133,7 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD
133133
}
134134

135135
if tag.Kind != ast.KindJSDocCallbackTag {
136-
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
136+
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
137137
}
138138
parameters := p.nodeSliceArena.NewSlice(0)
139139
for _, param := range jsSignature.Parameters() {
@@ -237,34 +237,25 @@ func (p *Parser) reparseJSDocComment(node *ast.Node, tag *ast.Node) {
237237
}
238238
}
239239

240-
func (p *Parser) gatherTypeParameters(j *ast.Node, tagWithTypeParameters *ast.Node) *ast.NodeList {
240+
func (p *Parser) gatherTypeParameters(j *ast.Node, typedefOrCallback bool) *ast.NodeList {
241241
var typeParameters []*ast.Node
242242
pos := -1
243243
endPos := -1
244244
firstTemplate := true
245-
// type parameters only apply to the tag or node they occur before, so record a place to stop
246-
start := 0
247-
for i, other := range j.AsJSDoc().Tags.Nodes {
248-
if other == tagWithTypeParameters {
249-
break
250-
}
251-
if other.Kind == ast.KindJSDocTypedefTag || other.Kind == ast.KindJSDocCallbackTag || other.Kind == ast.KindJSDocOverloadTag {
252-
start = i + 1
253-
}
254-
}
255-
for i, tag := range j.AsJSDoc().Tags.Nodes {
256-
if tag == tagWithTypeParameters {
257-
break
245+
for _, tag := range j.AsJSDoc().Tags.Nodes {
246+
// When a JSDoc comment contains an `@typedef` or `@callback` tag, `@template` type parameter
247+
// declarations apply to the type being defined.
248+
if !typedefOrCallback && (ast.IsJSDocTypedefTag(tag) || ast.IsJSDocCallbackTag(tag)) {
249+
return nil
258250
}
259-
if i < start || tag.Kind != ast.KindJSDocTemplateTag {
251+
if !ast.IsJSDocTemplateTag(tag) {
260252
continue
261253
}
262254
if firstTemplate {
263255
pos = tag.Pos()
264256
firstTemplate = false
265257
}
266258
endPos = tag.End()
267-
268259
constraint := tag.AsJSDocTemplateTag().Constraint
269260
firstTypeParameter := true
270261
for _, tp := range tag.TypeParameters() {
@@ -403,19 +394,19 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
403394
case ast.KindJSDocTemplateTag:
404395
if fun := getFunctionLikeHost(parent); fun != nil {
405396
if fun.TypeParameters() == nil && fun.FunctionLikeData().FullSignature == nil {
406-
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
397+
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
407398
p.finishMutatedNode(fun)
408399
}
409400
} else if parent.Kind == ast.KindClassDeclaration {
410401
class := parent.AsClassDeclaration()
411402
if class.TypeParameters == nil {
412-
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
403+
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
413404
p.finishMutatedNode(parent)
414405
}
415406
} else if parent.Kind == ast.KindClassExpression {
416407
class := parent.AsClassExpression()
417408
if class.TypeParameters == nil {
418-
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
409+
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
419410
p.finishMutatedNode(parent)
420411
}
421412
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//// [tests/cases/compiler/jsDocGenericOverloads.ts] ////
2+
3+
=== main.js ===
4+
const createElementC = /**
5+
>createElementC : Symbol(createElementC, Decl(main.js, 0, 5))
6+
7+
* @template {keyof HTMLElementTagNameMap} T
8+
* @param {T}t
9+
* @param {NodeList|HTMLCollection=}c
10+
*
11+
* @overload
12+
* @param {T}t
13+
* @return {HTMLElementTagNameMap[T]}
14+
*
15+
* @overload
16+
* @param {T}t
17+
* @param {NodeList|HTMLCollection}c
18+
* @return {HTMLElementTagNameMap[T]}
19+
*/(t, c) => {
20+
>t : Symbol(t, Decl(main.js, 13, 5))
21+
>c : Symbol(c, Decl(main.js, 13, 7))
22+
23+
/* ... omitted for brevity ... */ return document.createElement(t)
24+
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
25+
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
26+
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
27+
>t : Symbol(t, Decl(main.js, 13, 5))
28+
}
29+
30+
/**
31+
* @template {keyof HTMLElementTagNameMap} T
32+
* @param {T}t
33+
* @param {NodeList|HTMLCollection=}c
34+
*
35+
* @overload
36+
* @param {T}t
37+
* @return {HTMLElementTagNameMap[T]}
38+
*
39+
* @overload
40+
* @param {T}t
41+
* @param {NodeList|HTMLCollection}c
42+
* @return {HTMLElementTagNameMap[T]}
43+
*/
44+
function createElementF(t, c) {
45+
>createElementF : Symbol(createElementF, Decl(main.js, 22, 4), Decl(main.js, 26, 4), Decl(main.js, 15, 2))
46+
>t : Symbol(t, Decl(main.js, 31, 24))
47+
>c : Symbol(c, Decl(main.js, 31, 26))
48+
49+
/* ... omitted for brevity ... */ return document.createElement(t)
50+
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
51+
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
52+
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
53+
>t : Symbol(t, Decl(main.js, 31, 24))
54+
}
55+
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//// [tests/cases/compiler/jsDocGenericOverloads.ts] ////
2+
3+
=== main.js ===
4+
const createElementC = /**
5+
>createElementC : <T extends keyof HTMLElementTagNameMap>(t: T, c?: (NodeList | HTMLCollection) | undefined) => HTMLElementTagNameMap[T]
6+
7+
* @template {keyof HTMLElementTagNameMap} T
8+
* @param {T}t
9+
* @param {NodeList|HTMLCollection=}c
10+
*
11+
* @overload
12+
* @param {T}t
13+
* @return {HTMLElementTagNameMap[T]}
14+
*
15+
* @overload
16+
* @param {T}t
17+
* @param {NodeList|HTMLCollection}c
18+
* @return {HTMLElementTagNameMap[T]}
19+
*/(t, c) => {
20+
>(t, c) => { /* ... omitted for brevity ... */ return document.createElement(t) } : <T extends keyof HTMLElementTagNameMap>(t: T, c?: (NodeList | HTMLCollection) | undefined) => HTMLElementTagNameMap[T]
21+
>t : T
22+
>c : HTMLCollection | NodeList | undefined
23+
24+
/* ... omitted for brevity ... */ return document.createElement(t)
25+
>document.createElement(t) : HTMLElementTagNameMap[T]
26+
>document.createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
27+
>document : Document
28+
>createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
29+
>t : T
30+
}
31+
32+
/**
33+
* @template {keyof HTMLElementTagNameMap} T
34+
* @param {T}t
35+
* @param {NodeList|HTMLCollection=}c
36+
*
37+
* @overload
38+
* @param {T}t
39+
* @return {HTMLElementTagNameMap[T]}
40+
*
41+
* @overload
42+
* @param {T}t
43+
* @param {NodeList|HTMLCollection}c
44+
* @return {HTMLElementTagNameMap[T]}
45+
*/
46+
function createElementF(t, c) {
47+
>createElementF : { <T_1 extends keyof HTMLElementTagNameMap>(t: T_1): HTMLElementTagNameMap[T_1]; <T_1 extends keyof HTMLElementTagNameMap>(t: T_1, c: NodeList | HTMLCollection): HTMLElementTagNameMap[T_1]; }
48+
>t : T
49+
>c : HTMLCollection | NodeList | undefined
50+
51+
/* ... omitted for brevity ... */ return document.createElement(t)
52+
>document.createElement(t) : HTMLElementTagNameMap[T]
53+
>document.createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
54+
>document : Document
55+
>createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
56+
>t : T
57+
}
58+

testdata/baselines/reference/conformance/jsdocVariadicInOverload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export declare class Processor<ParseTree extends Node | undefined = undefined, H
127127
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
128128
* Current processor.
129129
*/
130-
use(preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>;
130+
use<Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>;
131131
/**
132132
* @overload
133133
* @param {string | null | undefined} [preset]

testdata/baselines/reference/conformance/jsdocVariadicInOverload.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export class Processor {
4141
* Current processor.
4242
*/
4343
use(value, ...parameters) {
44-
>use : { (preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>; <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(plugin: number, ...parameters: (Parameters | [boolean])): Processor; }
44+
>use : { <Parameters_1 extends Array<unknown> = [], Input_1 extends Node | string | undefined = undefined, Output_1 = Input_1>(preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>; <Parameters_1 extends Array<unknown> = [], Input_1 extends Node | string | undefined = undefined, Output_1 = Input_1>(plugin: number, ...parameters: (Parameters_1 | [boolean])): Processor; }
4545
>value : string | number | boolean | null | undefined
4646
>parameters : unknown[]
4747

@@ -64,9 +64,9 @@ var x = 1, y = 2, z = 3;
6464

6565
p.use(x, y, z);
6666
>p.use(x, y, z) : Processor<undefined, undefined, undefined, undefined, undefined>
67-
>p.use : { (preset?: string | null | undefined): Processor<undefined, undefined, undefined, undefined, undefined>; <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(plugin: number, ...parameters: Parameters | [boolean]): Processor; }
67+
>p.use : { <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(preset?: string | null | undefined): Processor<undefined, undefined, undefined, undefined, undefined>; <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(plugin: number, ...parameters: Parameters | [boolean]): Processor; }
6868
>p : Processor<undefined, undefined, undefined, undefined, undefined>
69-
>use : { (preset?: string | null | undefined): Processor<undefined, undefined, undefined, undefined, undefined>; <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(plugin: number, ...parameters: Parameters | [boolean]): Processor; }
69+
>use : { <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(preset?: string | null | undefined): Processor<undefined, undefined, undefined, undefined, undefined>; <Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(plugin: number, ...parameters: Parameters | [boolean]): Processor; }
7070
>x : number
7171
>y : number
7272
>z : number

testdata/baselines/reference/submodule/compiler/contravariantOnlyInferenceFromAnnotatedFunctionJs.errors.txt

Lines changed: 0 additions & 42 deletions
This file was deleted.

testdata/baselines/reference/submodule/compiler/contravariantOnlyInferenceFromAnnotatedFunctionJs.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
*/
1616
function foo(fns) {
1717
>foo : <A, B extends Record<string, unknown>>(fns: Funcs<A, B>) => [A, B]
18-
>fns : any
18+
>fns : Funcs<A, B>
1919

2020
return /** @type {any} */ (null);
2121
>(null) : any
2222
}
2323

2424
const result = foo({
25-
>result : [any, Record<string, unknown>]
26-
>foo({ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },}) : [any, Record<string, unknown>]
25+
>result : [string, { bar: string; }]
26+
>foo({ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },}) : [string, { bar: string; }]
2727
>foo : <A, B extends Record<string, unknown>>(fns: Funcs<A, B>) => [A, B]
2828
>{ bar: { fn: /** @param {string} a */ (a) => {}, thing: "asd", },} : { bar: { fn: (a: string) => void; thing: string; }; }
2929

0 commit comments

Comments
 (0)