Public reusable parsing, indexing, resolution, and completion APIs in pkg/jsparse
pkg/jsparse is the reusable low-level analysis framework extracted from the inspector prototype. It provides parser-facing data structures and helper APIs that other tools can build on: static diagnostics, dev tooling, completion endpoints, and source-to-AST mapping workflows.
import "github.com/go-go-golems/go-go-goja/pkg/jsparse"
The framework intentionally excludes Bubble Tea UI and command-specific behavior.
Reusable (pkg/jsparse):
BuildIndex, NodeRecord, offset lookups)Resolve, Resolution)TSParser, TSNode)ExtractCompletionContext, ResolveCandidates)Analyze, AnalysisResult)Tool-specific (cmd/inspector/app):
This boundary is the key contract: parser-centric features should depend on pkg/jsparse, not inspector command internals.
For user-facing workflow orchestration (document lifecycle, runtime merge, tree sync wrappers), prefer pkg/inspectorapi on top of pkg/jsparse.
pkg/jsparse vs pkg/inspectorapiUse pkg/jsparse when you need:
Use pkg/inspectorapi when you need:
pkg/jsparse + inspector packagespkg/jsparse intentionally combines:
parser.ParseFile) for typed AST + lexical scope resolutionTSParser.Parse) for error-tolerant cursor-local completion contextThis gives semantic correctness for bindings while keeping completion usable on partially typed code.
result := jsparse.Analyze("example.js", source, nil)
if result.ParseErr != nil {
for _, d := range result.Diagnostics() {
log.Printf("%s: %s", d.Severity, d.Message)
}
}
AnalysisResult contains:
Program (*ast.Program) when parse produced an AST (even partial)Index for node navigation and source mappingResolution for lexical bindings and referencesParseErr plus normalized Diagnostics() outputnode := result.NodeAtOffset(offset)
if node != nil {
fmt.Printf("%s %s [%d,%d)\n", node.Kind, node.Label, node.Start, node.End)
}
Use this for cursor-hover inspectors, error annotation, and source navigation tools.
High-level convenience methods:
tsParser, _ := jsparse.NewTSParser()
defer tsParser.Close()
root := tsParser.Parse([]byte(source))
ctx := result.CompletionContextAt(root, row, col)
candidates := result.CompleteAt(root, row, col)
Low-level primitives (same behavior, more control):
ctx := jsparse.ExtractCompletionContext(root, []byte(source), row, col)
candidates := jsparse.ResolveCandidates(ctx, result.Index, root)
AnalysisResult.CompletionContextAt and AnalysisResult.CompleteAt delegate to those low-level functions.
Index:
map[NodeID]*NodeRecord)NodeAtOffset)VisibleNodes, ExpandTo, AncestorPath)ToggleExpand)Source, LineColToOffset, OffsetToLineCol)Resolution:
Scopes, RootScopeID)NodeBinding)Unresolved)BindingForNode, IsDeclaration, IsReference, IsUnresolved)CompletionCandidate:
LabelKind (CandidateProperty, CandidateMethod, CandidateVariable, CandidateFunction, CandidateKeyword)Detail (display hint)NodeRecord contains source, tree, and display data:
ID, Kind, Label, SnippetStart, End (1-based offsets, end-exclusive)StartLine, StartCol, EndLine, EndCol (1-based)ParentID, ChildIDs, DepthExpanded (auto-expanded for depth < 2 when index is built)Scope kinds:
ScopeGlobalScopeFunctionScopeBlockScopeCatchScopeForBinding kinds:
BindingVarBindingLetBindingConstBindingFunctionBindingClassBindingParameterBindingCatchParamHoisting behavior:
var and function declarations resolve to nearest function/global scopelet and const remain block scopedBinding helpers:
BindingForNode(nodeID) -> binding for an identifier nodeIsDeclaration(nodeID) / IsReference(nodeID) / IsUnresolved(nodeID)AllUsages() on BindingRecord returns declaration + referencesres := jsparse.Analyze("file.js", source, nil)
if res.Index == nil || res.Resolution == nil {
return
}
node := res.NodeAtOffset(offset)
if node == nil {
return
}
binding := res.Resolution.BindingForNode(node.ID)
if binding != nil {
usages := binding.AllUsages()
_ = usages
}
NewTSParser creates a JavaScript tree-sitter parser.Parse returns an owning snapshot tree (*TSNode).TSNode snapshots survive parser close/reparse (they are not raw tree_sitter.Node handles).NodeAtPosition(row, col)HasError()ChildCount()Parse (simple full reparse for current use case).ResolveCandidates supports optional drawer-local declarations through variadic drawerRoot ...*TSNode.ExtractDrawerBindings(root) extracts local variable/function declarations from drawer CST.Object, Array, String, console, Math, JSONres := jsparse.Analyze("file.js", source, nil)
node := res.NodeAtOffset(offset)
if node == nil {
return
}
path := res.Index.AncestorPath(node.ID)
res.Index.ExpandTo(node.ID)
line, col := res.Index.OffsetToLineCol(node.Start)
roundTrip := res.Index.LineColToOffset(line, col)
_ = path
_ = roundTrip
Analyze is deterministic for a given source/options under current parser/index ordering invariants.Determinism invariant:
OrderedByStart sort behaviorCompletionArgument is currently reserved and yields no candidates.completion.go.Analyze.Diagnostics() first.Index is non-nil, map diagnostic offsets to nearby node metadata.If you need structured parse positions, inspect concrete parser error types:
if errList, ok := res.ParseErr.(parser.ErrorList); ok && len(errList) > 0 {
first := errList[0]
_ = first.Position()
}
TSParser.CompletionContextAt and CompleteAt per request.Diagnostics() severity error.Resolution.Unresolved.| Problem | Cause | Solution |
|---|---|---|
ParseErr is non-nil and no index present | Parse failed before any partial AST was returned | Render Diagnostics(), inspect res.ParseErr, fix syntax at reported position |
Completion candidates are empty after . | cursor/row/col do not point to member_expression or nearby ERROR fallback | retry at cursor and cursor-1; verify root.NodeAtPosition(row,col) and root.HasError() |
Missing symbol links in Resolution | properties (obj.foo) are not lexical identifiers | use lexical resolution for identifiers, property completion for member access |
| Candidates miss local drawer vars | ResolveCandidates called without drawer CST | pass drawer root: ResolveCandidates(ctx, idx, drawerRoot) |
| Inconsistent behavior across modules | workspace masking or mismatched module graph | run with GOWORK=off and rerun go mod tidy |
glaze help inspectorapi-hybrid-service-guideglaze help inspector-example-user-guidegoja-repl help repl-usageglaze help async-patterns