Skip to content

feat(parser): add JuliaSyntax -> CSTParser compatibility layer#118

Draft
pfitzseb wants to merge 51 commits into
mainfrom
sp/juliasyntax-cst-converter
Draft

feat(parser): add JuliaSyntax -> CSTParser compatibility layer#118
pfitzseb wants to merge 51 commits into
mainfrom
sp/juliasyntax-cst-converter

Conversation

@pfitzseb

@pfitzseb pfitzseb commented Jul 8, 2026

Copy link
Copy Markdown
Member

This is basically just Fable doing its thing. Haven't reviewed any of this in-depth, but generally seems to be sensible. This should allow for a gradual cutover of the static analysis passes to pure JuliaSyntax trees (presumably just the green tree?).

pfitzseb and others added 30 commits July 6, 2026 13:31
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements leaf-flattening pass that converts JuliaSyntax GreenNode tree
to flat vector of non-trivia tokens with folded trivia spans.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the flat Task 3 build_cst with a recursive assemble/assemble_form
pass over the green tree, computing spans from absolute leaf positions.
Covers toplevel/file wrapping (incl. semicolon-joined statement sequences,
which drop `;` into the preceding statement's fullspan), infix and prefix
calls, binary `=`, `begin...end` blocks, and parenthesized expressions.
Also folds quoted-literal merging into the recursive walk so string/char
oracle parity keeps holding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Adds check_spans (EXPR fullspan/span bookkeeping invariant, layout-
independent so it holds even for broken code) and the CSTCorpus test
module, which diffs converter output against CSTParser.parse per-file
and writes a markdown report grouped by diff signature plus the
accumulated UNHANDLED_KINDS.

Also fixes the AT_SIGN/ATSIGN mismatch between generic_form's
punctuation-trivia list and terminal_expr's actual output, and closes
two terminal-completeness gaps found while getting the smoke test
green: JuliaSyntax K"error" (zero-width diagnostic markers in broken
code, now an :errortoken leaf) and the MacroName/StringMacroName/
CmdMacroName kinds (mapped to :IDENTIFIER, matching CSTParser, which
has no separate concept for them).

Baseline corpus run over src/ (65 files): 0 passed, 61 failed, 4
errored. Expected at this stage -- the converter only implements
file/toplevel/call/=/block/parens + terminals, so every real file
falls through generic_form's fallback layout. Full report and top
diff signatures in .superpowers/sdd/task-5-report.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds assemble_form branches for function/struct/abstract/primitive/macro/
module definitions, where-clauses, ::/<: declarations, -> and short-form
`=` function defs (with implicit block-wrap), const/global/local, and
call's parameters/kwarg handling. Includes the rider fix to check_spans
(recurse into EXPR-headed x.head).

cst-conv filter: 80/80 passing (was 59; +21 new definitions snippets).
Corpus (src/, 65 files): 0/65 passed, same as baseline (still >= baseline);
UNHANDLED_KINDS shrank 52 -> 41.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
…uble block wrap

The `;` width before call kwargs now folds onto the leaf immediately
preceding it by position (via new Cursor terminals/kid_ranges) and its
parent chain, instead of descending args[end] — which missed trivia
leaves like closing parens/brackets. Explicit begin-end bodies of `->`
and short-form `=` defs are no longer wrapped in a second block.

cst-conv filter: 87/87 (was 80; +7 review repros). Corpus: 0/65,
unchanged from baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
…p trivia

The K"parameters" branch now folds its own `;` width (generic
widen_at_leaf! + Cursor.trim applied by assemble), so the span invariant
holds under tuple/dotcall/macrocall/curly parents, not just calls.
Sibling `;` groups nest recursively per the oracle (merge_params!, with
CSTParser's stored-last-arg span convention). The toplevel `;` fold now
widens the preceding statement's rightmost leaf chain, not just its
root. Empty parameters groups get trivia === nothing.

Adds a src/-wide check_spans sweep to the invariants testitem.
cst-conv filter: 157/157. Corpus: 0/65, unchanged from baseline;
invariant sweep 0 failures (fixes src/StaticLint/includes.jl hit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Adds dotcall, getfield (with quotenode), curly, macrocall (with @-name
fusion and string/cmd-macro name mangling), do-blocks, splat, and bare-type
2-kid ::. Fixes a pre-existing bug where block's `;` was kept as trivia
instead of folded away like toplevel's. Corpus: 0/65 -> 3/65 (first real
passes); cst-conv suite: 157 -> 184 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
The second of two adjacent `;` folded its width onto the first (already
dropped) `;` leaf — an orphan EXPR — losing the width and breaking the
childsum invariant in block, toplevel, and parameters folds. New
fold_semi! walks back past dropped separators; empty parameters groups
from bare extra `;` now collapse away like the oracle's.

Suite 189/189; corpus unchanged at 3/65; src/ sweep 0 invariant failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
if/elseif, ternary, while, for (+ iteration specs and cartesian_iterator),
try/catch/finally/else, let, return/break/continue, tuple/vect/hcat/vcat/
ncat/nrow, comprehension/generator/filter, and a paren-block special case.
Also fixes a pre-existing K"call" gap (parenless prefix-op calls like `!x`
kept empty trivia instead of nothing) found via corpus probing.

cst-conv suite: 225/225 (was 189). Corpus: 4/65 (was 3/65).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
…ariant, flatten

- K"?" reuses the kind for nested composite ternaries; guard trivia filing
  with the same leaf check as if/elseif (args === nothing).
- New Cursor.grow_span (span-only, clamped at fullspan, same set/apply/reset
  lifecycle as trim/trim_span): bare return's span reaches fullspan via its
  synthetic NOTHING arg, and a trivia-less enclosing block is measured to
  that arg too (grow_span_to_last_arg!).
- check_spans: skip the childsum check for childless nodes (bare matrix
  cells carry width with empty args/trivia); matrix literals added to the
  invariant testitem.
- Multi-for generators: oracle nests them inverted under :flatten wrappers
  with discontiguous hand-built spans; previous output broke check_spans.

cst-conv suite: 236/236 (was 225). Corpus: 4/65 (unchanged). Sweep clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Clears the converter's last crashing inputs (triple-quoted strings, cmd
literals/macros) and the biggest remaining oracle-diff bucket (compound
assignment, short-circuit, dotted ops, comparison chains, juxtaposition).

Test: cst-conv filter 276/276 (was 236 before this task). Corpus:
total=65, passed=8 (was 4), errored=0 (was 4) — no more crashing inputs.
No gate snippet needed @test_broken; two non-gate corpus findings recorded
in cst-converter-divergences.md (cmd $(...) re-scan approximation,
pre-existing raw/regex string unescape bug).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Review round-2 fixes:
- Macro-wrapped strings (raw""/r""/b"") carry RAW_STRING_FLAG on the
  K"string" node — gate the escape pipeline on it, applying only
  CSTParser's unescape_prefixed (backslash-run halving before quote/end),
  matching the oracle's prefixed path exactly. Fixes val corruption for
  r"\d+", raw"a\nb", b"\x00", r"[/\\]+" (real corpus hit in compat.jl).
- A BARE string literal as the whole $(...) subexpression keeps its
  :string wrapper in CSTParser (explicit re-wrap in parse_string_or_cmd);
  undo our single-chunk collapse at both interp sites (string parens path
  and the cmd $-rescan).

Test: cst-conv filter 286/286 (10 new snippets). Corpus: total=65,
passed=9 (was 8), errored=0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
…us burn-down

Adds branches for using/import/importpath/as/doc/quote(block+colon)/export,
tuple/vect/ref/braces/typed_comprehension/typed_hcat/vcat/ncat, var"...",
dotted comparison fusion, and fixes: macrocall args keep '=' as assignment,
matrix composite cells get empty trivia, nested where, local/global commas,
word-operator quotenodes, file-level trailing ';' span, bare-return span
growth in short-circuit/'='/toplevel, empty-body elseif span, qualified
macrocall span, leading-';' NOTHING placeholder.

Corpus: package src/ 65/65 identical to oracle (was 9/65); depot 1037/1161
(was 202/1161, 40 errored). cst-conv suite 371/371.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, vect assignment

- prefix operator applied to a single parenthesized arg unwraps to a call
  (except -/!/~ which keep the bracketed operand)
- K"inert" interpolated getfield field names → quotenode
- Float32 literals mapped to :FLOAT
- word operators (in/isa/where) in selective imports and importpath → OPERATOR
- vect elements keep = as assignment (like tuples)

Corpus: depot 1086/1161 (was 1037), 1 errored (JuliaSyntax parser overflow).
cst-conv suite 384/384.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…macrocall semicolon span

- explicit for/while branches (generic_form misfiled keyword-kinded conditions)
- begin as an index → BEGIN literal (mirrors end→END); quoted field/symbol stays IDENTIFIER
- matrix row span measured to last cell (handles whitespace before ;)
- return-type-annotated short function defs wrap body in a block
- var"..." quoted as a symbol → quotenode
- bare @m macrocall (NOTHING last arg) grows span when a ; folds onto it

Corpus: depot 1111/1161, src 65/65. cst-conv suite 400/400.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- :.& etc. fuse the dotted operator to one OPERATOR inside a quotenode
- -/!/~ with a directly-parenthesized operand (macro/function defs) keep it as brackets

Corpus: depot 1114/1161, src 65/65. cst-conv suite 405/405.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… fresh precompile

terminals.jl uses Cursor in method signatures (collect_quoted_pieces/assemble_quoted);
it was included before assembly.jl (which defines Cursor), which fails a cold
precompile. A stale cache had masked it. Reorder so the type is bound first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onst nesting, N-ary juxtaposition

Corpus: depot 1120/1161, src 65/65. cst-conv suite 413/413.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corpus: depot 1128/1161, src 65/65. cst-conv suite 418/418.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…icolon fold

Register the trailing chunk at the close leaf's terminal slot so a ;-fold
onto an interpolated string keeps childsums balanced (was breaking check_spans).

Corpus: depot 1133/1161, src 65/65. cst-conv suite 421/421.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tration for folds

Fixes check_spans failures on 'let x=1; y end' (semicolon separator was dropped)
and '(Base.@m; x)' (fused qualified macro name not registered for the fold).

Corpus: depot 1137/1161, src 65/65, 0 check_spans failures. cst-conv suite 426/426.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ti-row matrix)

Corpus: depot 1143/1161, src 65/65. cst-conv suite 429/429.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corpus: depot 1144/1161, src 65/65. cst-conv suite 432/432.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corpus: depot 1145/1161, src 65/65. cst-conv suite 435/435.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion defs

Corpus: depot 1147/1161, src 65/65. cst-conv suite 440/440.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng semicolon

Corpus: depot 1148/1161, src 65/65. cst-conv suite 442/442.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corpus: depot 1150/1161, src 65/65. cst-conv suite 443/443.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pfitzseb and others added 21 commits July 7, 2026 13:14
…n malformed input

Corpus: depot 1150/1161, src 65/65. cst-conv suite 445/445.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s-var stays block)

Corpus: depot 1152/1161, src 65/65. cst-conv suite 447/447.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corpus: depot 1153/1161, src 65/65. cst-conv suite 449/449.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Depot corpus 1153/1161 (was 202), package src 65/65 (was 9), 0 check_spans
failures; 4 documented parser divergences + 3 context-dependent cases remain.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ast arg)

Closes inherited deferred items 'begin return; end' and 'f(x) do y; return; end'.

Corpus: depot 1153/1161, src 65/65, 0 check_spans failures. cst-conv suite 451/451.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fix-op call unwrap

Round-2 review fixes:
- a parenless macrocall with a real last arg grows its span to that arg,
  so the qualified-macrocall span quirk propagates through nesting
  (x = @eval M.@m(a)); gated on last(args).fullspan != 0 so bare
  unqualified macrocalls (@run_package_tests) keep the normal exclusion
- .+(x)/.!(x) unwrap a parenthesized operand like +(x) (no -/!/~ exception
  for dotted ops); .+(a,b) fuses the dotted-op callee to one OPERATOR
- divergence log: script.jl reduced to 'struct S\nend;1' statement-group
  split (accept drift); tokenize_utils StackOverflow flagged as Task 12
  backend-flip liability; broadcast.jl reviewer-confirmed context-dependent

Corpus: depot 1154/1161 (blas.jl flipped), src 65/65, check_spans sweep
clean. cst-conv suite 456/456.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No oracle equality for broken code; contract is never throw, spans tile,
error subtrees stay traversable. Adds the K"error" non-terminal branch to
assemble_form, a zero-width-leaf fallback in terminal_expr, and fixes 8
crash/childsum bugs surfaced by prefix-sweeping the converter's own source
files: repeated "@"+name macro-fusion crashes on a missing/errored macro
name, module/struct catch-all clobbering the real name on a trailing
error marker, an under-bounded K"parens" shape, an unbounded interpolation
scan, and unbounded merge_quoted/merge_var closing-quote scans that could
run past their own node into a sibling's leaves. Adds a general
patch_dropped_width! safety net for any remaining childsum shortfall.

Gates: 12 brief snippets + 3 degenerate whole-file cases all pass
(never-throw, fullspan tiles, check_spans clean, walk-traversable).
cst-conv suite 510/510 (was 456). src corpus 65/65, depot corpus
1154/1161 (both unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Error kids inside a K"string" node (invalid escape, missing-close marker)
fell into collect_quoted_pieces' close-quote branch, which never resets
have_run — a second visit re-emitted the same content run as an
overlapping chunk (children sum > fullspan, found by a reviewer prefix
sweep over depot files with triple-quoted docstrings). Error kids now
extend the literal content run instead, with a synthesized zero-width
close when no closing quote exists; the close branch resets have_run.

Also: bare quote leaves under an error node (`x = "`) hit
punctuation_head's KeyError — unmapped kinds now map to :errortoken
(wrong heads still surface via oracle diffs; a throw killed the file).
patch_dropped_width! is now gated on the green subtree containing an
error node (checked lazily, only when a gap exists), so childsum bugs on
valid code keep failing check_spans instead of being padded over.

Gates: suite 534/534 (was 510; 6 new broken snippets). Corpora unchanged
(src 65/65, depot 1154/1161). Stride-5 prefix sweep over all 86
CommonMark+BandedMatrices files with triple-quoted docstrings: clean
(was 11 failing). Full re-sweep of converter source prefixes: clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Kind/API renames discovered empirically against the vendored 1.0.2 fork:
K"true"/K"false" -> K"Bool" (split by literal text); absent-catch-var
false-leaf -> K"Placeholder"; K"cartesian_iterator" -> K"iteration" (now
also wraps single specs); iteration specs get their own K"in" node kind;
short-form defs normalize to K"function" (keyword-child discriminates the
long form); compound assignment splits into op+= leaf pairs under K"op="
(dotted variant adds a `.` leaf, same kind + DOTTED flag); operator tokens
in value position reclassify to K"Identifier" (Base.isoperator on the text
recovers OPERATOR); getfield field names lose their K"quote" wrapper unless
colon-prefixed (a.$f also loses K"inert"); do-blocks nest under the
call/macrocall node instead of wrapping it; `->` LHS params always arrive
as K"tuple" (bare/parened/paren-block spellings mapped back to the
oracle's identifier/brackets/paren-block shapes); bare backtick literals
lose the core_@cmd wrapper (globalrefcmd macrocall synthesized, except for
the cmd-macro body position); struct-field docstrings arrive as K"doc"
(unfused back to sibling string+field, oracle only fuses at
toplevel/module/begin scope).

Suite 534/534; src corpus 65/65; depot corpus 1154/1161 (same six
accepted-drift files as under 0.4.10 plus the known StackOverflow file).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
The converter and layer code are 1.x-only after the port; drop the 0.4
compat entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Every Task-10 entry reproduces under the vendored 1.0.2 fork + CSTParser
3.5.1-DEV oracle; none dissolved. Two entries updated: the StackOverflow
file still overflows at default stack (but parses AND converts to full
oracle equality on an enlarged-stack task — verified Task 12 fix path),
and the "context-dependent" StaticArrays broadcast case now reduces to
whitespace-before-:: in a macro arg (CSTParser deviates from Meta.parse;
accept drift).

Final numbers: suite 534/534, src corpus 65/65, depot corpus 1154/1161,
stride-5 prefix sweep over 4 triple-quote-heavy files 14740 prefixes /
0 throws / 0 span violations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
derived_julia_legacy_syntax_tree now builds EXPR via CSTConversion from
the shared green tree; JW_CST_BACKEND=cstparser restores the old parser.
Parse and the recursive tree builders downstream run on an enlarged-stack
task so deeply-nested/machine-generated input never StackOverflows the
Salsa query; a shallow error-fallback tree covers the never-throw case.

Fixes two converter bugs found by the backend-flip regression sweep:
a.end/a.begin dot-getfield field names wrongly kept the END/BEGIN
index-context literal, and a module missing its closing `end` leaked a
4th :module arg instead of CSTParser's own missing-token convention,
which crashed StaticLint/completions code walking the tree via
CSTParser's iterate protocol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Five StaticLint tests relied on CSTParser leniently parsing invalid
Julia (unbracketed `=` in if conditions; stray `end` inside
comprehensions). JuliaSyntax rejects these at parse time, so the
check_if_conds tests now assert syntax-error diagnostics instead of the
now-unreachable EqInIfConditional lint hit, and the list-comp iter-spec
fixture is corrected to valid syntax with its lint assertions kept.
Also note on CST_BACKEND that the env var is read at load time only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
F1: unterminated for/while/try/function/macro/if leaked the missing-`end`
error node into args, breaking CSTParser's iterate accessors with a
BoundsError (killing get_diagnostics_blocking for the whole workspace).
Route it to a trivia :END placeholder instead (guarded by !has_end so a
terminated form with an internal parse error keeps its iterable shape); a
degenerate unterminated try synthesizes an empty catch for its fixed arity.
F7: drop the zero-width error-leaf recovery marker from a plain block's args
(begin/quote blocks keep an :END placeholder in trivia).
F2: fold the pre-`end` `;` in abstract/primitive type decls.
F3: key the short-form def discriminator on a genuine `function` keyword leaf.
F4/F5: reclassify isa/in/where word operators in comparison chains and
export/public lists to OPERATOR.
F6: bare unary-`::` typed def wraps its body block with EXPR[] trivia.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
F9: has_error_descendant re-walked the green subtree at every ancestor of a
dropped-width site (quadratic on broken input like "f("^n). Precompute once
per build a prefix count of error-covered leaves (leaves inside or being any
green error node, since a non-terminal K"error" can wrap ordinary tokens) and
test overlap in O(1), plus the node's own is-error kind for zero-width
childless error non-terminals. Fillers appear identically; timing is now
linear (~2x per input doubling vs the old ~4x).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
F8: it took 1-based bytes while its documented counterpart get_expr1 is
0-based, and byte 0 / out-of-range silently returned the root. Switch to a
0-based offset (converted internally), document it, and throw ArgumentError
for offset < 0 or offset >= file size.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
- broken-code invariants: recursive CSTParser-iterate walk applied to every
  broken snippet (would have caught F1), plus the unterminated for/while/try/
  function/macro repros and nested variants.
- workspace-level testitem: get_diagnostics_blocking on an unterminated block
  returns without throwing.
- oracle-green regressions for F2-F6; word-operator reclassification testitem.
- F8 syntax_node_at: 0-based offset assertions + boundary cases.
- F10: direct EqInIfConditional coverage via a lenient CSTParser parse,
  exercising the JW_CST_BACKEND=cstparser escape-hatch path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
A dropped ternary error marker can carry folded trailing trivia (the newline
in "a ? b\n" folds into the zero-width marker); dropping its width left a
childsum gap that patch_dropped_width! filled with a width-bearing errortoken
ARG, breaking the 3-arg/2-trivia arity CSTParser's ternary iterate expects
(and killing whole-workspace diagnostics via the include walker). Fold the
width onto the preceding real leaf instead (widen_at_leaf!, skipping earlier
markers); an orphaned width rides on the first zero-width pad. Tests cover
the repro variants plus trailing-trivia forms of the other crash-class
snippets and a workspace-level get_diagnostics_blocking assert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
A juxtaposition error ("1 x") makes JuliaSyntax close the body block and emit
the leftover tokens as an error sibling between the block and `end`, giving
terminated containers (function/let/module/struct/while/for/macro) an extra
kid that breaks CSTParser's fixed iterate arities; try and if+else variants
left dirty childsums. Absorb width-bearing error kids into the preceding
block centrally in assemble (skipping zero-span recovery markers, which keep
their per-form missing-`end` handling), and make the unterminated-`if` END
placeholder unconditional on a missing `end` since the absorbed kid can no
longer be its trigger. Tests cover all probed container shapes plus a
workspace-level get_diagnostics_blocking assert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k4W3Mkzub3WB2YUcA8L1Z
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant