-
Notifications
You must be signed in to change notification settings - Fork 0
Internals and Contributing
This page is for people changing es-parser's internals — the file-level mechanics the conceptual pages summarize. Every item is anchored to source; when prose and source disagree, the source wins.
The parser builds nodes / extra_data / scope_events / node_end_toks /
parent_fixups into over-sized buffers (pre-sized to tokens.len * 3/4,
parser.zig:657) and hands them to the Ast without a shrinking realloc. Each
buffer's true backing capacity is recorded in a paired *_cap field
(extra_data_cap, scope_events_cap, …, ast.zig:728) so deinit frees the real
allocation, not the logical length. Consumers never touch this — they call
Ast.deinit — but a contributor who adds a transferred buffer must add its _cap
and free it the same way. Tokens are the exception: the Ast borrows them and
frees nothing.
ref_event_idx maps a node to the reference event most recently emitted for it,
so a speculative reference can be cancelled (an identifier that turns out to be an
arrow parameter). It uses 0 as the "none" sentinel with a +1 bias: the array
is @memset to 0 (parser.zig:703), a write stores event_idx + 1
(parser.zig:1335), and a read treats 0 as "none", otherwise using idx - 1
(parser.zig:1681, :1747). The bias is what lets 0 be both the zero-initialized
state and the "no event" sentinel.
Both live in parseBinaryExpression under if (!p.is_ts):
?? mixing (expressions.zig:5968, :5975, :6024)
reject ?? whose left is logical_or / logical_and
reject || / && whose left is nullish_coalesce
reject ?? whose right is logical_or / logical_and
** unary base (expressions.zig:5988)
reject ** whose left ∈ { delete_expr, typeof_expr, void_expr,
logical_not, bitwise_not, unary_plus,
unary_minus, await_expr }
(prefix_inc / prefix_dec are intentionally NOT in the set)
recordTokMut (parser.zig:8675) logs a (idx, tag, start) triple only while
record_tok_muts is set — which it is during speculative type-argument parsing.
The in-place rewrite (tags_ptr[idx] = …) is unconditional; the flag controls only
whether it is journalled for undoTokMuts. A rewrite on a committed path is
permanent and deliberately unlogged. If you add a speculative parse that mutates
tokens, set and restore record_tok_muts around it (see tryParseTsTypeArguments,
expressions.zig:7524) or your rewrite won't be undone on backtrack.
The resolver's reference cache and undo machinery assume events arrive in a fixed
order per scope: scope_open before the declares and references inside it, each
matched by exactly one scope_close. undo_stacks[depth] restores scope_map and
ref_cache on close, so an emitter that opens a scope without closing it — or
emits a reference outside any open scope — corrupts visibility. Pair every emit
when adding a construct that opens a scope.
checkRedeclarations (event_resolver.zig:1398) runs only when diagnose_redeclare
is set and returns immediately for TypeScript input
(if (ast.is_ts) return &.{};, event_resolver.zig:1411). That bail is how TS declaration merging avoids false
duplicates. On JavaScript it runs several passes: same-scope lexical
duplicates, lexical-vs-var across the block/var boundary, duplicate block
functions with the Annex B B.3.3.4 exemption, parameter-vs-body, and
catch-param-vs-var per B.3.5.
CodePathBuilder.Result (code_path.zig:1772) is flat SoA plus a moved-in arena —
finish transfers the arena instead of copying each column, and deinit frees
just the arena. A subtlety for anyone touching segment edges: of the prev-edge
ranges, all_prev / prev / collapsed_prev are set at createSegment and never
change, but looped_prev is written later by markLooped (code_path.zig:747) when
a loop's back-edge is added. Segment is 36 bytes (9 × u32), SoA-stored.
- The parser is split across
expressions.zig,typescript.zig, andjsx.zig, which call back intoParsermethods markedpubonly to cross Zig's file boundary. Apub fnonParseris not public API; the supported surface isParser.parse/parseWithOptions/parseWithLanguage*and thesemantic.SemanticAnalyzerfacade. - This page's
file:lineanchors are its contract with the source; update them when you move the definitions they point at.
es-parser — MIT licensed. This wiki documents the implementation under src/; when a detail matters, the source is authoritative.