Skip to content

fix!: restrict class heritage expressions and preserve recovery boundaries - #145

Merged
exKAZUu merged 5 commits into
mainfrom
fix/class-heritage-expressions
Oct 27, 2026
Merged

exKAZUu merged 5 commits into
mainfrom
fix/class-heritage-expressions

Conversation

@exKAZUu

@exKAZUu exKAZUu commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Why

ECMAScript's ClassHeritage production takes a left-hand-side expression. Published 15.0.1 accepts class C extends B < 1 {} and class C extends B + D {} without errors, although Node 24 rejects them. Correct the operand category and preserve the following declarations in the unfinished-heritage example reported in WillBooster/tree-sitter#224.

Requirements

  • required: Review eligible fork and upstream issues, PRs, and recent changes, checking existing PRs to prevent duplicates.
  • required: Open a dedicated PR from latest main for each eligible item, ignoring backward compatibility.
  • required: Run review-booster and complete-pr concurrently, then release the changes.
  • required: Correct class heritage's acceptance of unparenthesized comparison/operator operands without changing global runtime recovery costs.
  • required: Preserve the following top-level class and exports in the reported unfinished extends B< example, including incremental edits.
  • required: Preserve valid JSX heritage, continuation tokens, public expression/primary-expression queries, fresh/incremental agreement, and termination on incomplete prefixes.
  • chosen: Share atom/call/member/subscript construction factories while giving heritage its own reference-only recursive graph and excluding a bare arrow as a superclass. Keep ordinary expression productions unchanged; their generated grammar JSON is identical to the reviewed head. Transparent wrappers retain both public expression query paths without introducing visible wrapper nodes.
  • chosen: End a completed superclass with a stateless zero-width scanner token when no member, subscript, call, template or optional-chain continuation follows. This local boundary preserves the following declarations in the measured recovery case; the initial syntax-only restriction swallowed a following export through unfinished JSX recovery.
  • chosen: Let normal comments remain ordinary lexer extras and delegate HTML-comment recognition to the existing extra-token scanner, including closing comments separated from a preceding newline by block comments. Do not impose a second partial line-start rule in the heritage guard; general HTML-comment eligibility remains the existing scanner contract. Defer the boundary before a same-line function keyword so async function and generator atoms stay intact; the existing async-keyword scanner retains its line-terminator restriction.
  • chosen: Keep context-reserved await/yield spellings as keyword lookahead even when the reduced operand category does not accept their keyword production: returning false allows the internal lexer to fall back to an identifier. Use dedicated heritage-only await/yield identifier externals for that decision; the existing scanner goal/function state still decides keyword versus identifier, while ordinary identifiers and property words retain their original permissive scanner roles. Reusing property-identifier validity also forced keyword tokens into object/class names and ordinary constructors, so that alternative is rejected.
  • chosen: Keep the comment cleanup: corpus checks already guard the named fragment nodes, and the removed HTML-length claim (29) is false for the current WHATWG reference CounterClockwiseContourIntegral (31 letters), while the code itself permits 30. Restoring that fact or comments repeating tested CST structure would violate the repository comment rule.
  • chosen: Keep scanner roles separated: only the dedicated heritage identifier symbols may force forbidden keyword lookahead; ordinary property-name and constructor symbols must retain their pre-existing fallback. Trivia must not terminate a valid member/call/template/subscript continuation. The property/method/field/destructuring matrix, existing incremental constructor test, mixed HTML/block-comment controls, reserved heritage controls and unchanged corpus enforce those invariants; dedicated heritage symbols plus the existing HTML extra scanner satisfy them without extra serialized state.

Customer Summary

Invalid operator operands in class heritage produce errors. Parenthesized expressions, constructors, calls, optional chains and JSX heritage remain supported, and the reported incomplete comparison keeps its following declarations separate.

Technical Summary

Heritage has a recursive reference-only graph; shared atom/call/member/subscript factories keep the ordinary expression productions unchanged. Transparent aliases preserve expression/primary query ancestry. The stateless _class_heritage_end token preserves the local recovery boundary, defers async function continuations, and lets slash comments remain lexer extras while emitting HTML comments as existing extras. Heritage atom identifiers distinguish await keywords from identifiers; dedicated heritage-only identifier tokens prevent context-reserved await/yield from falling back to ordinary identifiers without changing property-name or constructor handling. Both parser goals are regenerated canonically, without serialization or runtime-cost changes.

Testing

  • bun run verify and canonical bun run build-wasm passed.
  • All 207 native corpus cases pass without snapshot changes; both scanner entry points compile as strict C11.
  • The heritage, constructor, await-context and yield-context files pass all 25 tests, including valid async/generator atoms, HTML-comment continuations, Script await identifiers, reserved keyword operands, expression/primary queries, edit/undo, exact following-class/export recovery, and incomplete EOF prefixes. Class-field tests and the actual generated source-extension consumers also passed before the review repairs.
  • The 200,000-program Acorn/Babel comparison with V8 validation passes after the repairs; it caught the initial async-function regression in CI.
  • Native edit fuzz passes all 207 corpus cases with 100 iterations/up to five edits and seed 1. The initial sweep also passed with recorded seed 12106977561683125466. Full suites remain with PR CI.
  • Node 24 and Babel 8.0.7 with JSX confirm the corresponding syntax controls manually. Native keyword probes also reject the reserved operands.

Notes

Source grammars inheriting the external-symbol list must append CLASS_HERITAGE_END, CLASS_HERITAGE_YIELD_IDENTIFIER and CLASS_HERITAGE_AWAIT_IDENTIFIER after LINE_BREAK_BEFORE_FROM before their own symbols, and implement the end guard if they inherit class_heritage. Superclass syntax extensions must use the heritage-specific atom/call/member/subscript rules; ordinary expression extensions alone do not widen that graph. The public runtime language ABI and serialized scanner state are unchanged. Current TypeScript main imports its own vendored JavaScript grammar and pins npm JavaScript 11.0.0, so its published package is unaffected; adopting this source requires separate scanner alignment.

Retained code-gauge errors in touched code are src/scanner.c file NCSS (1044) and scanner dispatcher complexity/size (130/133). Fixing them requires a broader scanner refactor or mechanically moving cohesive logic between files. The end guard's NCSS warning (36) remains because it is one lookahead-character dispatch; threshold-only splitting would scatter that decision. No exclusions were added.

exKAZUu and others added 5 commits October 28, 2026 01:52
Co-authored-by: WillBooster (Codex) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex) <agent@willbooster.com>
Co-authored-by: WillBooster (Codex) <agent@willbooster.com>
…tions

Co-authored-by: WillBooster (Codex) <agent@willbooster.com>
@exKAZUu exKAZUu changed the title fix!: restrict class heritage without losing following declarations fix!: restrict class heritage expressions and preserve recovery boundaries Oct 27, 2026
@exKAZUu exKAZUu self-assigned this Oct 27, 2026
@exKAZUu
exKAZUu merged commit c8ff189 into main Oct 27, 2026
12 checks passed
@exKAZUu
exKAZUu deleted the fix/class-heritage-expressions branch October 27, 2026 17:55
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