Skip to content
Merged
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
8 changes: 1 addition & 7 deletions internal/parser/jsdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ func (p *Parser) withJSDoc(node *ast.Node, hasJSDoc bool) []*ast.Node {
if !hasJSDoc {
return nil
}

if p.jsdocCache == nil {
p.jsdocCache = make(map[*ast.Node][]*ast.Node, strings.Count(p.sourceText, "/**"))
Copy link
Member

Choose a reason for hiding this comment

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

This presizing was important for perf; can this be retained somewhere? Maybe not, given we don't want to do this when we're not parsing out jsdoc.

(I can test the perf of this later)

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 presizing is a lot more important for maps. We now collect the JSDoc information in an array and then build the map as we finalize the source file. The array is subject to reallocation, but the map is preallocated in its exact final size. We could presize the array, but I doubt it matters much.

Copy link
Member

@jakebailey jakebailey Feb 16, 2026

Choose a reason for hiding this comment

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

I'll profile it later, not blocking

(This is a map, though)

} else if _, ok := p.jsdocCache[node]; ok {
panic("tried to set JSDoc on a node with existing JSDoc")
}
// Should only be called once per node
p.hasDeprecatedTag = false
ranges := GetJSDocCommentRanges(&p.factory, p.jsdocCommentRangesSpace, node, p.sourceText)
Expand All @@ -61,7 +55,7 @@ func (p *Parser) withJSDoc(node *ast.Node, hasJSDoc bool) []*ast.Node {
if p.scriptKind == core.ScriptKindJS || p.scriptKind == core.ScriptKindJSX {
p.reparseTags(node, jsdoc)
}
p.jsdocCache[node] = jsdoc
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: node, jsDocs: jsdoc})
return jsdoc
}
return nil
Expand Down
20 changes: 18 additions & 2 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ const (

type ParsingContexts int

type JSDocInfo struct {
parent *ast.Node
jsDocs []*ast.Node
}

type Parser struct {
scanner *scanner.Scanner
factory ast.NodeFactory
Expand All @@ -74,7 +79,7 @@ type Parser struct {
notParenthesizedArrow collections.Set[int]
nodeSlicePool core.Pool[*ast.Node]
stringSlicePool core.Pool[string]
jsdocCache map[*ast.Node][]*ast.Node
jsdocInfos []JSDocInfo
possibleAwaitSpans []int
jsdocCommentsSpace []string
jsdocCommentRangesSpace []ast.CommentRange
Expand Down Expand Up @@ -264,6 +269,7 @@ type ParserState struct {
contextFlags ast.NodeFlags
diagnosticsLen int
jsDiagnosticsLen int
jsdocInfosLen int
reparsedClonesLen int
statementHasAwaitIdentifier bool
hasParseError bool
Expand All @@ -275,6 +281,7 @@ func (p *Parser) mark() ParserState {
contextFlags: p.contextFlags,
diagnosticsLen: len(p.diagnostics),
jsDiagnosticsLen: len(p.jsDiagnostics),
jsdocInfosLen: len(p.jsdocInfos),
reparsedClonesLen: len(p.reparsedClones),
statementHasAwaitIdentifier: p.statementHasAwaitIdentifier,
hasParseError: p.hasParseError,
Expand All @@ -287,6 +294,7 @@ func (p *Parser) rewind(state ParserState) {
p.contextFlags = state.contextFlags
p.diagnostics = p.diagnostics[0:state.diagnosticsLen]
p.jsDiagnostics = p.jsDiagnostics[0:state.jsDiagnosticsLen]
p.jsdocInfos = p.jsdocInfos[0:state.jsdocInfosLen]
p.reparsedClones = p.reparsedClones[0:state.reparsedClonesLen]
p.statementHasAwaitIdentifier = state.statementHasAwaitIdentifier
p.hasParseError = state.hasParseError
Expand Down Expand Up @@ -386,12 +394,20 @@ func (p *Parser) finishSourceFile(result *ast.SourceFile, isDeclarationFile bool
result.NodeCount = p.factory.NodeCount()
result.TextCount = p.factory.TextCount()
result.IdentifierCount = p.identifierCount
result.SetJSDocCache(p.jsdocCache)
result.SetJSDocCache(p.createJSDocCache())
slices.SortFunc(p.reparsedClones, ast.CompareNodePositions)
result.ReparsedClones = slices.Clone(p.reparsedClones)
ast.SetExternalModuleIndicator(result, p.opts.ExternalModuleIndicatorOptions)
}

func (p *Parser) createJSDocCache() map[*ast.Node][]*ast.Node {
result := make(map[*ast.Node][]*ast.Node, len(p.jsdocInfos))
for _, info := range p.jsdocInfos {
result[info.parent] = info.jsDocs
}
return result
}

func (p *Parser) parseToplevelStatement(i int) *ast.Node {
p.statementHasAwaitIdentifier = false
statement := p.parseStatement()
Expand Down
6 changes: 3 additions & 3 deletions internal/parser/reparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
}
typeAlias.AsTypeAliasDeclaration().Type = t
p.finishReparsedNode(typeAlias, tag)
p.jsdocCache[typeAlias] = []*ast.Node{jsDoc}
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocCallbackTag:
Expand All @@ -108,7 +108,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod
typeAlias := p.factory.NewJSTypeAliasDeclaration(nil, p.addDeepCloneReparse(callbackTag.FullName), nil, functionType)
typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, tag)
p.finishReparsedNode(typeAlias, tag)
p.jsdocCache[typeAlias] = []*ast.Node{jsDoc}
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: typeAlias, jsDocs: []*ast.Node{jsDoc}})
typeAlias.Flags |= ast.NodeFlagsHasJSDoc
p.reparseList = append(p.reparseList, typeAlias)
case ast.KindJSDocImportTag:
Expand Down Expand Up @@ -239,7 +239,7 @@ func (p *Parser) reparseJSDocComment(node *ast.Node, tag *ast.Node) {
if comment := tag.CommentList(); comment != nil {
propJSDoc := p.factory.NewJSDoc(comment, nil)
p.finishReparsedNode(propJSDoc, tag)
p.jsdocCache[node] = []*ast.Node{propJSDoc}
p.jsdocInfos = append(p.jsdocInfos, JSDocInfo{parent: node, jsDocs: []*ast.Node{propJSDoc}})
node.Flags |= ast.NodeFlagsHasJSDoc
}
}
Expand Down