Skip to content

fix!: reject try statements without handlers, stray commas, and function or class expression statements - #164

Merged
exKAZUu merged 1 commit into
mainfrom
fix/reject-invalid-commas-try-statement-heads
Nov 3, 2026
Merged

exKAZUu merged 1 commit into
mainfrom
fix/reject-invalid-commas-try-statement-heads

Conversation

@exKAZUu

@exKAZUu exKAZUu commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Why

The shipped parsers (both tree-sitter-javascript.wasm and tree-sitter-javascript_script.wasm) parsed several inputs that ECMAScript rejects as SyntaxErrors into trees with no ERROR or MISSING node, for example try { a(); }, ({ a: 1,, b: 2 }), f(,), const { a,, b } = o;, function () { return 1; }, class {}, and function f() {}.x; as statements. The fork's maintenance scope requires invalid input to yield ERROR or MISSING nodes.

Requirements

  • required: try { a(); } (a try statement with neither catch nor finally) yields an ERROR or MISSING node in the Module and Script grammars.
  • required: stray commas yield an ERROR or MISSING node in object literals (({ a: 1,, b: 2 }), ({,})), call and new arguments (f(1,, 2), f(,), new T(,)), and object patterns (const { a,, b } = o;).
  • required: array literals and array patterns keep allowing elisions ([1,,2], const [a,,b] = x;), and a single trailing comma stays valid where ECMAScript allows it (f(1,), ({ a: 1, }), const { a, } = o;).
  • required: an expression statement that begins with function or class (e.g. function () { return 1; }, class {}) yields an ERROR or MISSING node.
  • required: named declarations, (function () {})(), (class {}), x = function () {}, and, in the Module grammar, export default function () {} / export default class {} keep parsing to the same correct trees.
  • required: every valid program keeps parsing to the same correct tree, and parsing stays linear-time.
  • chosen: the restriction on function and class is enforced by the external scanner, because an LR grammar can exclude these keywords from the start of an expression statement only by duplicating the whole left spine of the expression grammar.

Customer Summary

Parsing these invalid programs now reports syntax errors instead of returning error-free trees. Valid programs parse exactly as before.

Technical Summary

  1. try_statement (grammar.js) requires a catch_clause, a finally_clause, or both.
  2. object and arguments use a new trailingCommaSep helper (one or more items, then an optional trailing comma) instead of commaSep(optional(...)). object_pattern lists properties separated by single commas, optionally followed by a comma and a rest pattern. array and array_pattern are unchanged.
  3. Two new externals, _declaration_function_keyword and _declaration_class_keyword, are aliased to function and class in the named branches of function_declaration, generator_function_declaration (including their async forms), and class_declaration. The scanner (scan_declaration_keyword in src/scanner.c) emits them whenever a declaration may start there and the word is exactly function or class, so the parser cannot take the function or class expression path at a statement start. This rejects nameless functions and classes, function f() {}.x;, and @dec class {} as statements, while keeping export function, export default function () {}, and labeled or if-body function declarations (Annex B) valid. The scanner skips this during error recovery.

Node types and field names are unchanged (src/node-types.json did not change). The commit is marked breaking because error-recovery trees change for the inputs above.

What to check hardest: the scanner reaches scan_declaration_keyword through scan_contextual_keyword, i.e. after the existing automatic-semicolon and statement-boundary decisions. The differential test against Acorn/Babel (200,000 generated programs, 0 mismatches) and the corpus support that no valid declaration or expression start is affected.

Testing

  • bun run verify: passed.
  • bun run test (whole node and browser suite, including corpus, Wasm corpus, grammar extensions, examples, performance, and the Acorn/Babel differential test): 34 files, 177 tests passed.
  • Added test/unit/syntaxRestrictions.test.ts: for both grammars, the invalid inputs above have errors, and their valid look-alikes parse to the expected trees.
  • Updated the corpus entry Function calls to wrap its function expression in parentheses (as a statement, function(x, y) {}(a, b); is invalid), and Extra complex literals in expressions to its new error-recovery tree. Updated test/unit/defaultExports.test.ts, which expected nameless declarations without export default to parse without errors.

Notes

  • export { , } and import { , } from "m" still parse without errors (export_clause and named_imports use commaSep(...), optional(',')). This PR leaves them unchanged.
  • code-gauge lists pre-existing errors in src/scanner.c functions that this PR does not modify (scan_automatic_semicolon, scan_after_line_break, follows_yield_operand, scan_resource_binding, tree_sitter_javascript_external_scanner_scan, scan_let, scan_default_trivia). The file-level fileNcss allowance is raised from 976 to 992 for the added helper.

…ion or class expression statements

Require a catch or finally clause in try statements, allow no elision in object literals, object patterns, and
call or new arguments, and lex `function` and `class` at a declaration start as declaration keywords so that an
expression statement cannot begin with them.

BREAKING CHANGE: inputs that ECMAScript rejects in these ways now produce trees with ERROR or MISSING nodes, and
error recovery around them changes. Node types, field names, and trees of valid programs are unchanged.

Co-authored-by: WillBooster (Claude Code) <agent@willbooster.com>
@exKAZUu exKAZUu self-assigned this Nov 3, 2026
@exKAZUu
exKAZUu merged commit 0b66154 into main Nov 3, 2026
10 checks passed
@exKAZUu
exKAZUu deleted the fix/reject-invalid-commas-try-statement-heads branch November 3, 2026 16:42
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