Skip to content

Add overload tag #900

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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: 2 additions & 11 deletions internal/api/encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -693,13 +693,7 @@ func getChildrenPropertyMask(node *ast.Node) uint8 {
case ast.KindClassDeclaration:
n := node.AsClassDeclaration()
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
case ast.KindJSDocPropertyTag:
n := node.AsJSDocPropertyTag()
if n.IsNameFirst {
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1)
}
return (boolToByte(n.TypeExpression != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
case ast.KindJSDocParameterTag:
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
n := node.AsJSDocParameterTag()
if n.IsNameFirst {
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeExpression != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
Expand Down Expand Up @@ -745,10 +739,7 @@ func getNodeDefinedData(node *ast.Node) uint32 {
case ast.KindObjectLiteralExpression:
n := node.AsObjectLiteralExpression()
return uint32(boolToByte(n.MultiLine)) << 24
case ast.KindJSDocPropertyTag:
n := node.AsJSDocPropertyTag()
return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
case ast.KindJSDocParameterTag:
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
n := node.AsJSDocParameterTag()
return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
case ast.KindJsxText:
Expand Down
95 changes: 20 additions & 75 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,8 +544,8 @@ func (n *Node) Type() *Node {
return n.AsTemplateLiteralTypeSpan().Type
case KindJSDocTypeExpression:
return n.AsJSDocTypeExpression().Type
case KindJSDocPropertyTag:
return n.AsJSDocPropertyTag().TypeExpression
case KindJSDocParameterTag, KindJSDocPropertyTag:
return n.AsJSDocParameterTag().TypeExpression
case KindJSDocNullableType:
return n.AsJSDocNullableType().Type
case KindJSDocNonNullableType:
Expand Down Expand Up @@ -619,7 +619,7 @@ func (n *Node) TagName() *Node {
return n.AsJSDocCallbackTag().TagName
case KindJSDocOverloadTag:
return n.AsJSDocOverloadTag().TagName
case KindJSDocParameterTag:
case KindJSDocParameterTag, KindJSDocPropertyTag:
return n.AsJSDocParameterTag().TagName
case KindJSDocReturnTag:
return n.AsJSDocReturnTag().TagName
Expand All @@ -633,8 +633,6 @@ func (n *Node) TagName() *Node {
return n.AsJSDocTypedefTag().TagName
case KindJSDocSeeTag:
return n.AsJSDocSeeTag().TagName
case KindJSDocPropertyTag:
return n.AsJSDocPropertyTag().TagName
case KindJSDocSatisfiesTag:
return n.AsJSDocSatisfiesTag().TagName
case KindJSDocImportTag:
Expand Down Expand Up @@ -705,7 +703,7 @@ func (n *Node) CommentList() *NodeList {
return n.AsJSDocCallbackTag().Comment
case KindJSDocOverloadTag:
return n.AsJSDocOverloadTag().Comment
case KindJSDocParameterTag:
case KindJSDocParameterTag, KindJSDocPropertyTag:
return n.AsJSDocParameterTag().Comment
case KindJSDocReturnTag:
return n.AsJSDocReturnTag().Comment
Expand All @@ -719,8 +717,6 @@ func (n *Node) CommentList() *NodeList {
return n.AsJSDocTypedefTag().Comment
case KindJSDocSeeTag:
return n.AsJSDocSeeTag().Comment
case KindJSDocPropertyTag:
return n.AsJSDocPropertyTag().Comment
case KindJSDocSatisfiesTag:
return n.AsJSDocSatisfiesTag().Comment
case KindJSDocImportTag:
Expand Down Expand Up @@ -1512,10 +1508,6 @@ func (n *Node) AsJSDocTemplateTag() *JSDocTemplateTag {
return n.data.(*JSDocTemplateTag)
}

func (n *Node) AsJSDocPropertyTag() *JSDocPropertyTag {
return n.data.(*JSDocPropertyTag)
}

func (n *Node) AsJSDocParameterTag() *JSDocParameterTag {
return n.data.(*JSDocParameterTag)
}
Expand Down Expand Up @@ -1836,7 +1828,7 @@ func IsDeclarationNode(node *Node) bool {
return node.DeclarationData() != nil
}

// DeclarationBase
// ExportableBase

type ExportableBase struct {
LocalSymbol *Symbol // Local symbol declared by node (initialized by binding only for exported nodes)
Expand Down Expand Up @@ -9113,93 +9105,46 @@ func (node *JSDocTemplateTag) Clone(f NodeFactoryCoercible) *Node {

func (node *JSDocTemplateTag) TypeParameters() *TypeParameterList { return node.typeParameters }

// JSDocPropertyTag
type JSDocPropertyTag struct {
// JSDocParameterTag, which includes both @parameter and @property
type JSDocParameterTag struct {
JSDocTagBase
name *EntityName
IsBracketed bool
TypeExpression *TypeNode
IsNameFirst bool
}

func (f *NodeFactory) NewJSDocPropertyTag(tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
data := &JSDocPropertyTag{}
func (f *NodeFactory) newJSDocParameterOrPropertyTag(kind Kind, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
data := &JSDocParameterTag{}
data.TagName = tagName
data.name = name
data.IsBracketed = isBracketed
data.TypeExpression = typeExpression
data.IsNameFirst = isNameFirst
data.Comment = comment
return f.newNode(KindJSDocPropertyTag, data)
}

func (f *NodeFactory) UpdateJSDocPropertyTag(node *JSDocPropertyTag, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
if tagName != node.TagName || name != node.name || isBracketed != node.IsBracketed || typeExpression != node.TypeExpression || isNameFirst != node.IsNameFirst || comment != node.Comment {
return updateNode(f.NewJSDocPropertyTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *JSDocPropertyTag) ForEachChild(v Visitor) bool {
if node.IsNameFirst {
return visit(v, node.TagName) || visit(v, node.name) || visit(v, node.TypeExpression) || visitNodeList(v, node.Comment)
} else {
return visit(v, node.TagName) || visit(v, node.TypeExpression) || visit(v, node.name) || visitNodeList(v, node.Comment)
}
}

func (node *JSDocPropertyTag) VisitEachChild(v *NodeVisitor) *Node {
tagName := v.visitNode(node.TagName)
var name, typeExpression *Node
if node.IsNameFirst {
name, typeExpression = v.visitNode(node.name), v.visitNode(node.TypeExpression)
} else {
typeExpression, name = v.visitNode(node.TypeExpression), v.visitNode(node.name)
}
return v.Factory.UpdateJSDocPropertyTag(node, tagName, name, node.IsBracketed, typeExpression, node.IsNameFirst, v.visitNodes(node.Comment))
}

func (node *JSDocPropertyTag) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewJSDocPropertyTag(node.TagName, node.Name(), node.IsBracketed, node.TypeExpression, node.IsNameFirst, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
return f.newNode(kind, data)
}

func (node *JSDocPropertyTag) Name() *EntityName { return node.name }

// JSDocParameterTag
type JSDocParameterTag struct {
JSDocTagBase
name *EntityName
IsBracketed bool
TypeExpression *TypeNode
IsNameFirst bool
func (f *NodeFactory) NewJSDocParameterTag(tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
return f.newJSDocParameterOrPropertyTag(KindJSDocParameterTag, tagName, name, isBracketed, typeExpression, isNameFirst, comment)
}

func (f *NodeFactory) NewJSDocParameterTag(tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
data := &JSDocParameterTag{}
data.TagName = tagName
data.name = name
data.IsBracketed = isBracketed
data.TypeExpression = typeExpression
data.IsNameFirst = isNameFirst
data.Comment = comment
return f.newNode(KindJSDocParameterTag, data)
func (f *NodeFactory) NewJSDocPropertyTag(tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
return f.newJSDocParameterOrPropertyTag(KindJSDocPropertyTag, tagName, name, isBracketed, typeExpression, isNameFirst, comment)
}

func (f *NodeFactory) UpdateJSDocParameterTag(node *JSDocParameterTag, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
func (f *NodeFactory) UpdateJSDocParameterTag(kind Kind, node *JSDocParameterTag, tagName *IdentifierNode, name *EntityName, isBracketed bool, typeExpression *TypeNode, isNameFirst bool, comment *NodeList) *Node {
if tagName != node.TagName || name != node.name || isBracketed != node.IsBracketed || typeExpression != node.TypeExpression || isNameFirst != node.IsNameFirst || comment != node.Comment {
return updateNode(f.NewJSDocParameterTag(tagName, name, isBracketed, typeExpression, isNameFirst, comment), node.AsNode(), f.hooks)
return updateNode(f.newJSDocParameterOrPropertyTag(kind, tagName, name, isBracketed, typeExpression, isNameFirst, comment), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *JSDocParameterTag) ForEachChild(v Visitor) bool {
if visit(v, node.TagName) {
return true
}
if node.IsNameFirst {
return visit(v, node.name) || visit(v, node.TypeExpression) || visitNodeList(v, node.Comment)
return visit(v, node.TagName) || visit(v, node.name) || visit(v, node.TypeExpression) || visitNodeList(v, node.Comment)
} else {
return visit(v, node.TypeExpression) || visit(v, node.name) || visitNodeList(v, node.Comment)
return visit(v, node.TagName) || visit(v, node.TypeExpression) || visit(v, node.name) || visitNodeList(v, node.Comment)
}
}

Expand All @@ -9211,11 +9156,11 @@ func (node *JSDocParameterTag) VisitEachChild(v *NodeVisitor) *Node {
} else {
typeExpression, name = v.visitNode(node.TypeExpression), v.visitNode(node.name)
}
return v.Factory.UpdateJSDocParameterTag(node, tagName, name, node.IsBracketed, typeExpression, node.IsNameFirst, v.visitNodes(node.Comment))
return v.Factory.UpdateJSDocParameterTag(node.Kind, node, tagName, name, node.IsBracketed, typeExpression, node.IsNameFirst, v.visitNodes(node.Comment))
}

func (node *JSDocParameterTag) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewJSDocParameterTag(node.TagName, node.Name(), node.IsBracketed, node.TypeExpression, node.IsNameFirst, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().newJSDocParameterOrPropertyTag(node.Kind, node.TagName, node.Name(), node.IsBracketed, node.TypeExpression, node.IsNameFirst, node.Comment), node.AsNode(), f.AsNodeFactory().hooks)
}

func (node *JSDocParameterTag) Name() *EntityName { return node.name }
Expand Down
14 changes: 12 additions & 2 deletions internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2874,9 +2874,15 @@ func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextR
}
return scanner.GetRangeOfTokenAtPosition(sourceFile, pos)
// This list is a work in progress. Add missing node kinds to improve their error spans
case ast.KindFunctionDeclaration, ast.KindMethodDeclaration:
if node.Flags&ast.NodeFlagsReparsed != 0 {
errorNode = node
break
}
fallthrough
case ast.KindVariableDeclaration, ast.KindBindingElement, ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration,
ast.KindModuleDeclaration, ast.KindEnumDeclaration, ast.KindEnumMember, ast.KindFunctionDeclaration, ast.KindFunctionExpression,
ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindPropertyDeclaration,
ast.KindModuleDeclaration, ast.KindEnumDeclaration, ast.KindEnumMember, ast.KindFunctionExpression,
ast.KindGetAccessor, ast.KindSetAccessor, ast.KindTypeAliasDeclaration, ast.KindJSTypeAliasDeclaration, ast.KindPropertyDeclaration,
ast.KindPropertySignature, ast.KindNamespaceImport:
errorNode = ast.GetNameOfDeclaration(node)
case ast.KindArrowFunction:
Expand All @@ -2896,6 +2902,10 @@ func GetErrorRangeForNode(sourceFile *ast.SourceFile, node *ast.Node) core.TextR
pos := scanner.SkipTrivia(sourceFile.Text(), node.AsSatisfiesExpression().Expression.End())
return scanner.GetRangeOfTokenAtPosition(sourceFile, pos)
case ast.KindConstructor:
if node.Flags&ast.NodeFlagsReparsed != 0 {
errorNode = node
break
}
scanner := scanner.GetScannerForSourceFile(sourceFile, node.Pos())
start := scanner.TokenStart()
for scanner.Token() != ast.KindConstructorKeyword && scanner.Token() != ast.KindStringLiteral && scanner.Token() != ast.KindEndOfFile {
Expand Down
5 changes: 3 additions & 2 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3382,7 +3382,7 @@ func (c *Checker) checkFunctionOrConstructorSymbol(symbol *ast.Symbol) {
} else {
duplicateFunctionDeclaration = true
}
} else if previousDeclaration != nil && previousDeclaration.Parent == node.Parent && previousDeclaration.End() != node.Pos() {
} else if previousDeclaration != nil && previousDeclaration.Parent == node.Parent && previousDeclaration.End() != node.Pos() && previousDeclaration.Flags&ast.NodeFlagsReparsed == 0 {
reportImplementationExpectedError(previousDeclaration)
}
if bodyIsPresent {
Expand Down Expand Up @@ -18332,7 +18332,8 @@ func (c *Checker) getSignaturesOfSymbol(symbol *ast.Symbol) []*Signature {
// precedes the implementation node (i.e. has the same parent and ends where the implementation starts).
if i > 0 && decl.Body() != nil {
previous := symbol.Declarations[i-1]
if decl.Parent == previous.Parent && decl.Kind == previous.Kind && decl.Pos() == previous.End() {
if decl.Parent == previous.Parent && decl.Kind == previous.Kind &&
(decl.Pos() == previous.End() || previous.Flags&ast.NodeFlagsReparsed != 0) {
continue
}
}
Expand Down
74 changes: 57 additions & 17 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
case ast.KindJSDocTypeLiteral:
members := p.nodeSlicePool.NewSlice(0)
for _, member := range typeExpression.AsJSDocTypeLiteral().JSDocPropertyTags {
var questionToken *ast.TokenNode
if member.AsJSDocPropertyTag().IsBracketed ||
member.AsJSDocPropertyTag().TypeExpression != nil && member.AsJSDocPropertyTag().TypeExpression.Type().Kind == ast.KindJSDocOptionalType {
questionToken = p.factory.NewToken(ast.KindQuestionToken)
questionToken.Loc = core.NewTextRange(member.Pos(), member.End())
questionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
prop := p.factory.NewPropertySignatureDeclaration(nil, member.Name(), questionToken, member.Type(), nil /*initializer*/)
prop := p.factory.NewPropertySignatureDeclaration(nil, member.Name(), p.makeQuestionIfOptional(member.AsJSDocParameterTag()), member.Type(), nil /*initializer*/)
prop.Loc = member.Loc
prop.Flags = p.contextFlags | ast.NodeFlagsReparsed
members = append(members, prop)
Expand All @@ -86,7 +79,7 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
panic("typedef tag type expression should be a name reference or a type expression" + typeExpression.Kind.String())
}
typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, tag.AsJSDocTypedefTag().Name(), typeParameters, t)
typeAlias.Loc = core.NewTextRange(tag.Pos(), tag.End())
typeAlias.Loc = tag.Loc
typeAlias.Flags = p.contextFlags | ast.NodeFlagsReparsed
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocImportTag:
Expand All @@ -95,10 +88,48 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
importClause.Flags |= ast.NodeFlagsReparsed
importClause.AsImportClause().IsTypeOnly = true
importDeclaration := p.factory.NewJSImportDeclaration(importTag.Modifiers(), importClause, importTag.ModuleSpecifier, importTag.Attributes)
importDeclaration.Loc = core.NewTextRange(tag.Pos(), tag.End())
importDeclaration.Loc = tag.Loc
importDeclaration.Flags = p.contextFlags | ast.NodeFlagsReparsed
p.reparseList = append(p.reparseList, importDeclaration)
// !!! @overload and other unattached tags (@callback et al) support goes here
case ast.KindJSDocOverloadTag:
if fun, ok := getFunctionLikeHost(parent); ok {
jsSignature := tag.AsJSDocOverloadTag().TypeExpression.AsJSDocSignature()
typeParameters := p.gatherTypeParameters(j)
var signature *ast.Node
switch fun.Kind {
case ast.KindFunctionDeclaration, ast.KindFunctionExpression, ast.KindArrowFunction:
signature = p.factory.NewFunctionDeclaration(nil, nil, fun.Name(), typeParameters, nil, nil, nil)
case ast.KindMethodDeclaration, ast.KindMethodSignature:
signature = p.factory.NewMethodDeclaration(nil, nil, fun.Name(), nil, typeParameters, nil, nil, nil)
case ast.KindConstructor:
signature = p.factory.NewConstructorDeclaration(nil, typeParameters, nil, nil, nil)
default:
panic("Unexpected kind " + fun.Kind.String())
}

parameters := p.nodeSlicePool.NewSlice(0)
for _, param := range jsSignature.Parameters.Nodes {
jsparam := param.AsJSDocParameterTag()

var parameterType *ast.Node
if jsparam.TypeExpression != nil {
parameterType = p.makeNewType(jsparam.TypeExpression, signature)
}
parameter := p.factory.NewParameterDeclaration(nil, nil, jsparam.Name(), p.makeQuestionIfOptional(jsparam), parameterType, nil)
parameter.Loc = jsparam.Loc
parameter.Flags = p.contextFlags | ast.NodeFlagsReparsed
parameters = append(parameters, parameter)
}

if jsSignature.Type != nil {
signature.FunctionLikeData().Type = p.makeNewType(jsSignature.Type.AsJSDocReturnTag().TypeExpression, signature)
}
signature.FunctionLikeData().Parameters = p.newNodeList(jsSignature.Parameters.Loc, parameters)
signature.Loc = tag.AsJSDocOverloadTag().TagName.Loc
signature.Flags = p.contextFlags | ast.NodeFlagsReparsed
p.reparseList = append(p.reparseList, signature)
}
// !!! other unattached tags (@callback) support goes here
}
if !isLast {
continue
Expand Down Expand Up @@ -162,12 +193,11 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
if param, ok := findMatchingParameter(fun, jsparam); ok {
if param.Type() == nil {
param.AsParameterDeclaration().Type = p.makeNewType(jsparam.TypeExpression, param)
if param.AsParameterDeclaration().QuestionToken == nil &&
param.AsParameterDeclaration().Initializer == nil &&
(jsparam.IsBracketed || jsparam.TypeExpression != nil && jsparam.TypeExpression.Type().Kind == ast.KindJSDocOptionalType) {
param.AsParameterDeclaration().QuestionToken = p.factory.NewToken(ast.KindQuestionToken)
param.AsParameterDeclaration().QuestionToken.Loc = core.NewTextRange(param.End(), param.End())
param.AsParameterDeclaration().QuestionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
if param.AsParameterDeclaration().QuestionToken == nil &&
param.AsParameterDeclaration().Initializer == nil {
if question := p.makeQuestionIfOptional(jsparam); question != nil {
param.AsParameterDeclaration().QuestionToken = question
}
}
}
Expand All @@ -183,6 +213,16 @@ func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) {
}
}

func (p *Parser) makeQuestionIfOptional(parameter *ast.JSDocParameterTag) *ast.Node {
var questionToken *ast.Node
if parameter.IsBracketed || parameter.TypeExpression != nil && parameter.TypeExpression.Type().Kind == ast.KindJSDocOptionalType {
questionToken = p.factory.NewToken(ast.KindQuestionToken)
questionToken.Loc = parameter.Loc
questionToken.Flags = p.contextFlags | ast.NodeFlagsReparsed
}
return questionToken
}

func findMatchingParameter(fun *ast.Node, tag *ast.JSDocParameterTag) (*ast.Node, bool) {
for _, parameter := range fun.Parameters() {
if parameter.Name().Kind == ast.KindIdentifier && tag.Name().Kind == ast.KindIdentifier &&
Expand Down
Loading
Loading