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
36 changes: 36 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,42 @@ function f(cu) {
In Strada, `cu` incorrectly narrows to `C` inside the `if` block, unlike with TS assertion syntax.
In Corsa, the behaviour is the same between TS and JS.

#### `@overload` with arrow functions and function expressions

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:

```js
/**
* @overload
* @param {string} x
* @returns {string}
*
* @overload
* @param {number} x
* @returns {number}
*
* @param {string | number} x
* @returns {string | number}
*/
let f = x => x;
```

You should write:

```js
/**
* @type {{
* (x: string): string;
* (x: number): number;
* }}
* @param {string | number} x
* @returns {any}
*/
const f = x => x;
```

This works with both TS6 and TS7. Note the change to `any` for the return type annotation. This is to satisfy the assignment check.

### Expandos

#### Constructor functions are no longer supported
Expand Down
20 changes: 10 additions & 10 deletions internal/parser/jsdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -865,9 +865,10 @@ func (p *Parser) parseNestedTypeLiteral(typeExpression *ast.Node, name *ast.Enti
p.rewind(state)
break
}
if child.Kind == ast.KindJSDocParameterTag || child.Kind == ast.KindJSDocPropertyTag {
switch child.Kind {
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
children = append(children, child)
} else if child.Kind == ast.KindJSDocTemplateTag {
case ast.KindJSDocTemplateTag:
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
}
}
Expand Down Expand Up @@ -1007,11 +1008,11 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent
p.rewind(state)
break
}
if child.Kind == ast.KindJSDocTemplateTag {
break
}
hasChildren = true
if child.Kind == ast.KindJSDocTypeTag {
switch child.Kind {
case ast.KindJSDocTemplateTag:
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
case ast.KindJSDocTypeTag:
if childTypeTag == nil {
childTypeTag = child.AsJSDocTypeTag()
} else {
Expand All @@ -1020,9 +1021,8 @@ func (p *Parser) parseTypedefTag(start int, tagName *ast.IdentifierNode, indent
related := ast.NewDiagnostic(nil, core.NewTextRange(0, 0), diagnostics.The_tag_was_first_specified_here)
lastError.AddRelatedInfo(related)
}
break
}
} else {
default:
jsdocPropertyTags = append(jsdocPropertyTags, child)
}
}
Expand Down Expand Up @@ -1082,9 +1082,9 @@ func (p *Parser) parseCallbackTagParameters(indent int) *ast.NodeList {
}
if child.Kind == ast.KindJSDocTemplateTag {
p.parseErrorAtRange(child.TagName().Loc, diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag)
break
} else {
parameters = append(parameters, child)
}
parameters = append(parameters, child)
}
return p.newNodeList(core.NewTextRange(pos, p.nodePos()), parameters)
}
Expand Down
35 changes: 13 additions & 22 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
break
}
typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.addDeepCloneReparse(tag.AsJSDocTypedefTag().Name()), nil, nil)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
var t *ast.Node
switch typeExpression.Kind {
case ast.KindJSDocTypeExpression:
Expand All @@ -74,7 +74,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
}
functionType := p.reparseJSDocSignature(callbackTag.TypeExpression, tag, jsDoc, tag, nil)
typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.addDeepCloneReparse(callbackTag.FullName), nil, functionType)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/)
p.finishReparsedNode(typeAlias, tag)
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
Expand Down Expand Up @@ -119,7 +119,7 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD
}

if tag.Kind != ast.KindJSDocCallbackTag {
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
}
parameters := p.nodeSliceArena.NewSlice(0)
for _, param := range jsSignature.Parameters() {
Expand Down Expand Up @@ -223,34 +223,25 @@ func (p *Parser) reparseJSDocComment(node *ast.Node, tag *ast.Node) {
}
}

func (p *Parser) gatherTypeParameters(j *ast.Node, tagWithTypeParameters *ast.Node) *ast.NodeList {
func (p *Parser) gatherTypeParameters(j *ast.Node, typedefOrCallback bool) *ast.NodeList {
var typeParameters []*ast.Node
pos := -1
endPos := -1
firstTemplate := true
// type parameters only apply to the tag or node they occur before, so record a place to stop
start := 0
for i, other := range j.AsJSDoc().Tags.Nodes {
if other == tagWithTypeParameters {
break
}
if other.Kind == ast.KindJSDocTypedefTag || other.Kind == ast.KindJSDocCallbackTag || other.Kind == ast.KindJSDocOverloadTag {
start = i + 1
}
}
for i, tag := range j.AsJSDoc().Tags.Nodes {
if tag == tagWithTypeParameters {
break
for _, tag := range j.AsJSDoc().Tags.Nodes {
// When a JSDoc comment contains an `@typedef` or `@callback` tag, `@template` type parameter
// declarations apply to the type being defined.
if !typedefOrCallback && (ast.IsJSDocTypedefTag(tag) || ast.IsJSDocCallbackTag(tag)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if !typedefOrCallback && (ast.IsJSDocTypedefTag(tag) || ast.IsJSDocCallbackTag(tag)) {
// In some instances, JSDoc may contain multiple kinds of tags
// that permit type parameters through a `@template` tag.
// For example, a JSDoc comment may contain a `@typedef`
// and an `@overload`. In such cases, we assume that type parameters
// solely belong to `@typedef` and `@callback` tags.
if !typedefOrCallback && (ast.IsJSDocTypedefTag(tag) || ast.IsJSDocCallbackTag(tag)) {

return nil
}
if i < start || tag.Kind != ast.KindJSDocTemplateTag {
if !ast.IsJSDocTemplateTag(tag) {
continue
}
if firstTemplate {
pos = tag.Pos()
firstTemplate = false
}
endPos = tag.End()

constraint := tag.AsJSDocTemplateTag().Constraint
firstTypeParameter := true
for _, tp := range tag.TypeParameters() {
Expand Down Expand Up @@ -389,19 +380,19 @@ func (p *Parser) reparseHosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Node)
case ast.KindJSDocTemplateTag:
if fun := getFunctionLikeHost(parent); fun != nil {
if fun.TypeParameters() == nil && fun.FunctionLikeData().FullSignature == nil {
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
fun.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(fun)
}
} else if parent.Kind == ast.KindClassDeclaration {
class := parent.AsClassDeclaration()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(parent)
}
} else if parent.Kind == ast.KindClassExpression {
class := parent.AsClassExpression()
if class.TypeParameters == nil {
class.TypeParameters = p.gatherTypeParameters(jsDoc, nil /*tagWithTypeParameters*/)
class.TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/)
p.finishMutatedNode(parent)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//// [tests/cases/compiler/jsDocGenericOverloads.ts] ////

=== main.js ===
const createElementC = /**
>createElementC : Symbol(createElementC, Decl(main.js, 0, 5))

* @template {keyof HTMLElementTagNameMap} T
* @param {T}t
* @param {NodeList|HTMLCollection=}c
*
* @overload
* @param {T}t
* @return {HTMLElementTagNameMap[T]}
*
* @overload
* @param {T}t
* @param {NodeList|HTMLCollection}c
* @return {HTMLElementTagNameMap[T]}
*/(t, c) => {
>t : Symbol(t, Decl(main.js, 13, 5))
>c : Symbol(c, Decl(main.js, 13, 7))

/* ... omitted for brevity ... */ return document.createElement(t)
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>t : Symbol(t, Decl(main.js, 13, 5))
}

/**
* @template {keyof HTMLElementTagNameMap} T
* @param {T}t
* @param {NodeList|HTMLCollection=}c
*
* @overload
* @param {T}t
* @return {HTMLElementTagNameMap[T]}
*
* @overload
* @param {T}t
* @param {NodeList|HTMLCollection}c
* @return {HTMLElementTagNameMap[T]}
*/
function createElementF(t, c) {
>createElementF : Symbol(createElementF, Decl(main.js, 22, 4), Decl(main.js, 26, 4), Decl(main.js, 15, 2))
>t : Symbol(t, Decl(main.js, 31, 24))
>c : Symbol(c, Decl(main.js, 31, 26))

/* ... omitted for brevity ... */ return document.createElement(t)
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>t : Symbol(t, Decl(main.js, 31, 24))
}

58 changes: 58 additions & 0 deletions testdata/baselines/reference/compiler/jsDocGenericOverloads.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//// [tests/cases/compiler/jsDocGenericOverloads.ts] ////

=== main.js ===
const createElementC = /**
>createElementC : <T extends keyof HTMLElementTagNameMap>(t: T, c?: (NodeList | HTMLCollection) | undefined) => HTMLElementTagNameMap[T]

* @template {keyof HTMLElementTagNameMap} T
* @param {T}t
* @param {NodeList|HTMLCollection=}c
*
* @overload
* @param {T}t
* @return {HTMLElementTagNameMap[T]}
*
* @overload
* @param {T}t
* @param {NodeList|HTMLCollection}c
* @return {HTMLElementTagNameMap[T]}
*/(t, c) => {
>(t, c) => { /* ... omitted for brevity ... */ return document.createElement(t) } : <T extends keyof HTMLElementTagNameMap>(t: T, c?: (NodeList | HTMLCollection) | undefined) => HTMLElementTagNameMap[T]
>t : T
>c : HTMLCollection | NodeList | undefined

/* ... omitted for brevity ... */ return document.createElement(t)
>document.createElement(t) : HTMLElementTagNameMap[T]
>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; }
>document : 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; }
>t : T
}

/**
* @template {keyof HTMLElementTagNameMap} T
* @param {T}t
* @param {NodeList|HTMLCollection=}c
*
* @overload
* @param {T}t
* @return {HTMLElementTagNameMap[T]}
*
* @overload
* @param {T}t
* @param {NodeList|HTMLCollection}c
* @return {HTMLElementTagNameMap[T]}
*/
function createElementF(t, c) {
>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]; }
>t : T
>c : HTMLCollection | NodeList | undefined

/* ... omitted for brevity ... */ return document.createElement(t)
>document.createElement(t) : HTMLElementTagNameMap[T]
>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; }
>document : 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; }
>t : T
}

Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export declare class Processor<ParseTree extends Node | undefined = undefined, H
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
* Current processor.
*/
use(preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>;
use<Parameters extends Array<unknown> = [], Input extends Node | string | undefined = undefined, Output = Input>(preset?: string | null | undefined): Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>;
/**
* @overload
* @param {string | null | undefined} [preset]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Processor {
* Current processor.
*/
use(value, ...parameters) {
>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; }
>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; }
>value : string | number | boolean | null | undefined
>parameters : unknown[]

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

p.use(x, y, z);
>p.use(x, y, z) : Processor<undefined, undefined, undefined, undefined, undefined>
>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; }
>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; }
>p : Processor<undefined, undefined, undefined, undefined, undefined>
>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; }
>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; }
>x : number
>y : number
>z : number
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
*/
function foo(fns) {
>foo : <A, B extends Record<string, unknown>>(fns: Funcs<A, B>) => [A, B]
>fns : any
>fns : Funcs<A, B>

return /** @type {any} */ (null);
>(null) : any
}

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

Expand Down
Loading
Loading