Skip to content

Type nodes for JSDoc #1013

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 6 commits into from
Jun 9, 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
98 changes: 61 additions & 37 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func (n *Node) Expression() *Node {
case KindJsxSpreadAttribute:
return n.AsJsxSpreadAttribute().Expression
}
panic("Unhandled case in Node.Expression")
panic("Unhandled case in Node.Expression" + n.Kind.String())
Copy link
Preview

Copilot AI Jun 2, 2025

Choose a reason for hiding this comment

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

[nitpick] The panic message concatenates the kind string without a separator. For clarity, add a space or colon (e.g. "Unhandled case in Node.Expression: " + n.Kind.String()).

Suggested change
panic("Unhandled case in Node.Expression" + n.Kind.String())
panic("Unhandled case in Node.Expression: " + n.Kind.String())

Copilot uses AI. Check for mistakes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, good idea.

}

func (n *Node) ArgumentList() *NodeList {
Expand All @@ -376,7 +376,7 @@ func (n *Node) ArgumentList() *NodeList {
case KindNewExpression:
return n.AsNewExpression().Arguments
}
panic("Unhandled case in Node.Arguments")
panic("Unhandled case in Node.Arguments" + n.Kind.String())
}

func (n *Node) Arguments() []*Node {
Expand Down Expand Up @@ -518,6 +518,10 @@ func (n *Node) Type() *Node {
return n.AsPropertySignatureDeclaration().Type
case KindPropertyDeclaration:
return n.AsPropertyDeclaration().Type
case KindPropertyAssignment:
return n.AsPropertyAssignment().Type
case KindShorthandPropertyAssignment:
return n.AsShorthandPropertyAssignment().Type
case KindTypePredicate:
return n.AsTypePredicateNode().Type
case KindParenthesizedType:
Expand Down Expand Up @@ -552,7 +556,13 @@ func (n *Node) Type() *Node {
return n.AsJSDocNonNullableType().Type
case KindJSDocOptionalType:
return n.AsJSDocOptionalType().Type
case KindEnumMember, KindBindingElement, KindExportAssignment, KindJSExportAssignment, KindBinaryExpression, KindCommonJSExport:
case KindExportAssignment, KindJSExportAssignment:
return n.AsExportAssignment().Type
case KindCommonJSExport:
return n.AsCommonJSExport().Type
case KindBinaryExpression:
return n.AsBinaryExpression().Type
case KindEnumMember, KindBindingElement:
return nil
default:
funcLike := n.FunctionLikeData()
Expand Down Expand Up @@ -4430,46 +4440,48 @@ type ExportAssignment struct {
ModifiersBase
compositeNodeBase
IsExportEquals bool
Type *TypeNode // TypeNode. Only set by JSDoc @type tags.
Expression *Expression // Expression
}

func (f *NodeFactory) newExportOrJSExportAssignment(kind Kind, modifiers *ModifierList, isExportEquals bool, expression *Expression) *Node {
func (f *NodeFactory) newExportOrJSExportAssignment(kind Kind, modifiers *ModifierList, isExportEquals bool, typeNode *TypeNode, expression *Expression) *Node {
data := &ExportAssignment{}
data.modifiers = modifiers
data.IsExportEquals = isExportEquals
data.Type = typeNode
data.Expression = expression
return f.newNode(kind, data)
}

func (f *NodeFactory) NewExportAssignment(modifiers *ModifierList, isExportEquals bool, expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindExportAssignment, modifiers, isExportEquals, expression)
func (f *NodeFactory) NewExportAssignment(modifiers *ModifierList, isExportEquals bool, typeNode *TypeNode, expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindExportAssignment, modifiers, isExportEquals, typeNode, expression)
}

func (f *NodeFactory) NewJSExportAssignment(expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindJSExportAssignment, nil /*modifiers*/, true, expression)
func (f *NodeFactory) NewJSExportAssignment(t *TypeNode, expression *Expression) *Node {
return f.newExportOrJSExportAssignment(KindJSExportAssignment, nil /*modifiers*/, true, t, expression)
}

func (f *NodeFactory) UpdateExportAssignment(node *ExportAssignment, modifiers *ModifierList, expression *Expression) *Node {
if modifiers != node.modifiers || expression != node.Expression {
return updateNode(f.newExportOrJSExportAssignment(node.Kind, modifiers, node.IsExportEquals, expression), node.AsNode(), f.hooks)
func (f *NodeFactory) UpdateExportAssignment(node *ExportAssignment, modifiers *ModifierList, typeNode *TypeNode, expression *Expression) *Node {
if modifiers != node.modifiers || typeNode != node.Type || expression != node.Expression {
return updateNode(f.newExportOrJSExportAssignment(node.Kind, modifiers, node.IsExportEquals, typeNode, expression), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *ExportAssignment) ForEachChild(v Visitor) bool {
return visitModifiers(v, node.modifiers) || visit(v, node.Expression)
return visitModifiers(v, node.modifiers) || visit(v, node.Type) || visit(v, node.Expression)
}

func (node *ExportAssignment) VisitEachChild(v *NodeVisitor) *Node {
return v.Factory.UpdateExportAssignment(node, v.visitModifiers(node.modifiers), v.visitNode(node.Expression))
return v.Factory.UpdateExportAssignment(node, v.visitModifiers(node.modifiers), v.visitNode(node.Type), v.visitNode(node.Expression))
}

func (node *ExportAssignment) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().newExportOrJSExportAssignment(node.Kind, node.Modifiers(), node.IsExportEquals, node.Expression), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().newExportOrJSExportAssignment(node.Kind, node.Modifiers(), node.IsExportEquals, node.Type, node.Expression), node.AsNode(), f.AsNodeFactory().hooks)
}

func (node *ExportAssignment) computeSubtreeFacts() SubtreeFacts {
return propagateModifierListSubtreeFacts(node.modifiers) | propagateSubtreeFacts(node.Expression)
return propagateModifierListSubtreeFacts(node.modifiers) | propagateSubtreeFacts(node.Type) | propagateSubtreeFacts(node.Expression)
}

func IsExportAssignment(node *Node) bool {
Expand All @@ -4492,34 +4504,36 @@ type CommonJSExport struct {
ExportableBase
ModifiersBase
name *IdentifierNode
Type *TypeNode
Initializer *Expression
}

func (f *NodeFactory) NewCommonJSExport(modifiers *ModifierList, name *IdentifierNode, initializer *Expression) *Node {
func (f *NodeFactory) NewCommonJSExport(modifiers *ModifierList, name *IdentifierNode, typeNode *TypeNode, initializer *Expression) *Node {
data := &CommonJSExport{}
data.modifiers = modifiers
data.name = name
data.Type = typeNode
data.Initializer = initializer
return newNode(KindCommonJSExport, data, f.hooks)
}

func (f *NodeFactory) UpdateCommonJSExport(node *CommonJSExport, modifiers *ModifierList, name *IdentifierNode, initializer *Expression) *Node {
if modifiers != node.modifiers || initializer != node.Initializer || name != node.name {
return updateNode(f.NewCommonJSExport(node.modifiers, name, initializer), node.AsNode(), f.hooks)
func (f *NodeFactory) UpdateCommonJSExport(node *CommonJSExport, modifiers *ModifierList, name *IdentifierNode, typeNode *TypeNode, initializer *Expression) *Node {
if modifiers != node.modifiers || initializer != node.Initializer || name != node.name || typeNode != node.Type {
return updateNode(f.NewCommonJSExport(node.modifiers, name, typeNode, initializer), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *CommonJSExport) ForEachChild(v Visitor) bool {
return visitModifiers(v, node.modifiers) || visit(v, node.name) || visit(v, node.Initializer)
return visitModifiers(v, node.modifiers) || visit(v, node.name) || visit(v, node.Type) || visit(v, node.Initializer)
}

func (node *CommonJSExport) VisitEachChild(v *NodeVisitor) *Node {
return v.Factory.UpdateCommonJSExport(node, v.visitModifiers(node.modifiers), v.visitNode(node.name), v.visitNode(node.Initializer))
return v.Factory.UpdateCommonJSExport(node, v.visitModifiers(node.modifiers), v.visitNode(node.name), v.visitNode(node.Type), v.visitNode(node.Initializer))
}

func (node *CommonJSExport) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewCommonJSExport(node.Modifiers(), node.name, node.Initializer), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().NewCommonJSExport(node.Modifiers(), node.name, node.Type, node.Initializer), node.AsNode(), f.AsNodeFactory().hooks)
}

func IsCommonJSExport(node *Node) bool {
Expand Down Expand Up @@ -5544,44 +5558,50 @@ func (node *NoSubstitutionTemplateLiteral) Clone(f NodeFactoryCoercible) *Node {
type BinaryExpression struct {
ExpressionBase
DeclarationBase
ModifiersBase
compositeNodeBase
Left *Expression // Expression
Type *TypeNode // TypeNode. Only set by JSDoc @type tags.
OperatorToken *TokenNode // TokenNode
Right *Expression // Expression
}

func (f *NodeFactory) NewBinaryExpression(left *Expression, operatorToken *TokenNode, right *Expression) *Node {
func (f *NodeFactory) NewBinaryExpression(modifiers *ModifierList, left *Expression, typeNode *TypeNode, operatorToken *TokenNode, right *Expression) *Node {
if operatorToken == nil {
panic("operatorToken is required")
}
data := f.binaryExpressionPool.New()
data.modifiers = modifiers
data.Left = left
data.Type = typeNode
data.OperatorToken = operatorToken
data.Right = right
return f.newNode(KindBinaryExpression, data)
}

func (f *NodeFactory) UpdateBinaryExpression(node *BinaryExpression, left *Expression, operatorToken *TokenNode, right *Expression) *Node {
if left != node.Left || operatorToken != node.OperatorToken || right != node.Right {
return updateNode(f.NewBinaryExpression(left, operatorToken, right), node.AsNode(), f.hooks)
func (f *NodeFactory) UpdateBinaryExpression(node *BinaryExpression, modifiers *ModifierList, left *Expression, typeNode *TypeNode, operatorToken *TokenNode, right *Expression) *Node {
if left != node.Left || typeNode != node.Type || operatorToken != node.OperatorToken || right != node.Right {
return updateNode(f.NewBinaryExpression(modifiers, left, typeNode, operatorToken, right), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *BinaryExpression) ForEachChild(v Visitor) bool {
return visit(v, node.Left) || visit(v, node.OperatorToken) || visit(v, node.Right)
return visitModifiers(v, node.modifiers) || visit(v, node.Left) || visit(v, node.Type) || visit(v, node.OperatorToken) || visit(v, node.Right)
}

func (node *BinaryExpression) VisitEachChild(v *NodeVisitor) *Node {
return v.Factory.UpdateBinaryExpression(node, v.visitNode(node.Left), v.visitToken(node.OperatorToken), v.visitNode(node.Right))
return v.Factory.UpdateBinaryExpression(node, v.visitModifiers(node.modifiers), v.visitNode(node.Left), v.visitNode(node.Type), v.visitToken(node.OperatorToken), v.visitNode(node.Right))
}

func (node *BinaryExpression) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewBinaryExpression(node.Left, node.OperatorToken, node.Right), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().NewBinaryExpression(node.modifiers, node.Left, node.Type, node.OperatorToken, node.Right), node.AsNode(), f.AsNodeFactory().hooks)
}

func (node *BinaryExpression) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.Left) |
return propagateModifierListSubtreeFacts(node.modifiers) |
propagateSubtreeFacts(node.Left) |
propagateSubtreeFacts(node.Type) |
propagateSubtreeFacts(node.OperatorToken) |
propagateSubtreeFacts(node.Right) |
core.IfElse(node.OperatorToken.Kind == KindInKeyword && IsPrivateIdentifier(node.Left), SubtreeContainsClassFields, SubtreeFactsNone)
Expand Down Expand Up @@ -6658,39 +6678,42 @@ type PropertyAssignment struct {
NamedMemberBase
ObjectLiteralElementBase
compositeNodeBase
Type *TypeNode // TypeNode. Only set by JSDoc @type tags.
Initializer *Expression // Expression
}

func (f *NodeFactory) NewPropertyAssignment(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, initializer *Expression) *Node {
func (f *NodeFactory) NewPropertyAssignment(modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
data := f.propertyAssignmentPool.New()
data.modifiers = modifiers
data.name = name
data.PostfixToken = postfixToken
data.Type = typeNode
data.Initializer = initializer
return f.newNode(KindPropertyAssignment, data)
}

func (f *NodeFactory) UpdatePropertyAssignment(node *PropertyAssignment, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, initializer *Expression) *Node {
if modifiers != node.modifiers || name != node.name || postfixToken != node.PostfixToken || initializer != node.Initializer {
return updateNode(f.NewPropertyAssignment(modifiers, name, postfixToken, initializer), node.AsNode(), f.hooks)
func (f *NodeFactory) UpdatePropertyAssignment(node *PropertyAssignment, modifiers *ModifierList, name *PropertyName, postfixToken *TokenNode, typeNode *TypeNode, initializer *Expression) *Node {
if modifiers != node.modifiers || name != node.name || postfixToken != node.PostfixToken || typeNode != node.Type || initializer != node.Initializer {
return updateNode(f.NewPropertyAssignment(modifiers, name, postfixToken, typeNode, initializer), node.AsNode(), f.hooks)
}
return node.AsNode()
}

func (node *PropertyAssignment) ForEachChild(v Visitor) bool {
return visitModifiers(v, node.modifiers) || visit(v, node.name) || visit(v, node.PostfixToken) || visit(v, node.Initializer)
return visitModifiers(v, node.modifiers) || visit(v, node.name) || visit(v, node.PostfixToken) || visit(v, node.Type) || visit(v, node.Initializer)
}

func (node *PropertyAssignment) VisitEachChild(v *NodeVisitor) *Node {
return v.Factory.UpdatePropertyAssignment(node, v.visitModifiers(node.modifiers), v.visitNode(node.name), v.visitToken(node.PostfixToken), v.visitNode(node.Initializer))
return v.Factory.UpdatePropertyAssignment(node, v.visitModifiers(node.modifiers), v.visitNode(node.name), v.visitToken(node.PostfixToken), v.visitNode(node.Type), v.visitNode(node.Initializer))
}

func (node *PropertyAssignment) Clone(f NodeFactoryCoercible) *Node {
return cloneNode(f.AsNodeFactory().NewPropertyAssignment(node.Modifiers(), node.Name(), node.PostfixToken, node.Initializer), node.AsNode(), f.AsNodeFactory().hooks)
return cloneNode(f.AsNodeFactory().NewPropertyAssignment(node.Modifiers(), node.Name(), node.PostfixToken, node.Type, node.Initializer), node.AsNode(), f.AsNodeFactory().hooks)
}

func (node *PropertyAssignment) computeSubtreeFacts() SubtreeFacts {
return propagateSubtreeFacts(node.name) |
propagateSubtreeFacts(node.Type) |
propagateSubtreeFacts(node.Initializer)
}

Expand All @@ -6705,6 +6728,7 @@ type ShorthandPropertyAssignment struct {
NamedMemberBase
ObjectLiteralElementBase
compositeNodeBase
Type *TypeNode // TypeNode. Only set by JSDoc @type tags.
EqualsToken *TokenNode
ObjectAssignmentInitializer *Expression // Optional
}
Expand Down
11 changes: 11 additions & 0 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ func isDeclarationStatementKind(kind Kind) bool {
KindExportDeclaration,
KindExportAssignment,
KindJSExportAssignment,
KindCommonJSExport,
KindNamespaceExportDeclaration:
return true
}
Expand Down Expand Up @@ -859,6 +860,15 @@ func WalkUpParenthesizedTypes(node *TypeNode) *Node {
return node
}

func GetEffectiveTypeParent(parent *Node) *Node {
if IsInJSFile(parent) && parent.Kind == KindJSDocTypeExpression {
if host := parent.AsJSDocTypeExpression().Host; host != nil {
parent = host
}
}
return parent
}

// Walks up the parents of a node to find the containing SourceFile
func GetSourceFileOfNode(node *Node) *SourceFile {
for node != nil {
Expand Down Expand Up @@ -3231,6 +3241,7 @@ func ReplaceModifiers(factory *NodeFactory, node *Node, modifierArray *ModifierL
return factory.UpdateExportAssignment(
node.AsExportAssignment(),
modifierArray,
node.Type(),
node.Expression(),
)
case KindExportDeclaration:
Expand Down
5 changes: 4 additions & 1 deletion internal/binder/binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1649,7 +1649,7 @@ func (b *Binder) bindChildren(node *ast.Node) {
b.inAssignmentPattern = saveInAssignmentPattern
b.bindEachChild(node)
case ast.KindJSExportAssignment, ast.KindCommonJSExport:
return // Reparsed nodes do not double-bind children, which are not reparsed
// Reparsed nodes do not double-bind children, which are not reparsed
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this is an improvement--now setJSDocParents runs and inAssignmentPattern correctly follows dynamic scope

default:
b.bindEachChild(node)
}
Expand Down Expand Up @@ -2208,9 +2208,11 @@ func (b *Binder) bindDestructuringAssignmentFlow(node *ast.Node) {
b.bind(expr.Right)
b.inAssignmentPattern = true
b.bind(expr.Left)
b.bind(expr.Type)
} else {
b.inAssignmentPattern = true
b.bind(expr.Left)
b.bind(expr.Type)
b.inAssignmentPattern = false
b.bind(expr.OperatorToken)
b.bind(expr.Right)
Expand Down Expand Up @@ -2239,6 +2241,7 @@ func (b *Binder) bindBinaryExpressionFlow(node *ast.Node) {
}
} else {
b.bind(expr.Left)
b.bind(expr.Type)
if operator == ast.KindCommaToken {
b.maybeBindExpressionFlowIfCall(node)
}
Expand Down
Loading