Skip to main content

hermes_parser/js/
mod.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8//! The JS parser (`JSParserImpl`). Port of `lib/Parser/JSParserImpl*`.
9//! Recursive-descent LL(1) over `JSLexer`, building the `ast` ESTree.
10
11use std::cell::Cell;
12use std::collections::HashMap;
13use std::rc::Rc;
14
15use hermes_ast::context::GCLock;
16use hermes_ast::node::Node;
17use hermes_support::location::{SMLoc, SMRange};
18
19use crate::lexer::{GrammarContext, JSLexer};
20use crate::token_kinds::TokenKind;
21
22mod classes;
23mod expressions;
24mod flow;
25mod functions;
26mod jsx;
27mod modules;
28mod pre_lazy;
29mod statements;
30mod ts;
31
32pub use pre_lazy::ParserPass;
33use pre_lazy::PreParsedBufferInfo;
34
35/// Whether import/export declarations are allowed in this statement list.
36/// Port of `JSParserImpl::AllowImportExport`.
37#[derive(Clone, Copy, PartialEq, Eq, Debug)]
38#[allow(dead_code)] // `No` variant used in P2+ (block-statement parsing)
39pub(super) enum AllowImportExport {
40    Yes,
41    No,
42}
43
44/// Whether we are recursing into `new` expression parsing.
45/// Port of C++ `JSParserImpl::IsConstructorCall`.
46#[derive(Clone, Copy, PartialEq, Eq, Debug)]
47pub(super) enum IsConstructorCall {
48    No,
49    Yes,
50}
51
52/// Whether this LHS is being parsed as the `extends` clause of a class.
53/// Port of C++ `JSParserImpl::IsClassHeritageArgument`.
54/// P1 callers always pass `No`; P3+ (class parsing) will pass `Yes`.
55#[derive(Clone, Copy, PartialEq, Eq, Debug)]
56#[allow(dead_code)]
57pub(super) enum IsClassHeritageArgument {
58    No,
59    Yes,
60}
61
62/// A bitmask of grammar parameters threaded between parse functions.
63/// Port of `JSParserImpl::Param`.
64#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
65pub struct Param(u32);
66
67/// `[In]` — "in" is recognized as a binary operator in RelationalExpression.
68pub const PARAM_IN: Param = Param(1 << 0);
69/// `[Return]`
70pub const PARAM_RETURN: Param = Param(1 << 1);
71/// `[Default]`
72pub const PARAM_DEFAULT: Param = Param(1 << 2);
73/// `[Tagged]`
74pub const PARAM_TAGGED: Param = Param(1 << 3);
75
76impl Param {
77    /// Union (C++ `operator+`).
78    pub fn plus(self, b: Param) -> Param {
79        Param(self.0 | b.0)
80    }
81    /// Difference (C++ `operator-`).
82    pub fn minus(self, b: Param) -> Param {
83        Param(self.0 & !b.0)
84    }
85    /// True if any flag in `p` is set (C++ `has`).
86    pub fn has(self, p: Param) -> bool {
87        (self.0 & p.0) != 0
88    }
89    /// True if ALL flags in `p` are set (C++ `hasAll`).
90    pub fn has_all(self, p: Param) -> bool {
91        (self.0 & p.0) == p.0
92    }
93    /// `p` if any of its bits are set here, else empty (C++ `get`).
94    /// (The C++ variadic `get(p, tail...)` is just `a.get(x).plus(a.get(y))`
95    /// in Rust — single-arg `get` + `plus` cover it.)
96    pub fn get(self, p: Param) -> Param {
97        Param(self.0 & p.0)
98    }
99}
100
101/// Self-explanatory: the maximum depth of parser recursion. Port of
102/// `JSParserImpl::MAX_RECURSION_DEPTH` (JSParserImpl.h:189-202), whose C++
103/// `#ifdef` ladder is:
104/// ```text
105///   HERMES_LIMIT_STACK_DEPTH                            -> 128
106///   _MSC_VER && HERMES_SLOW_DEBUG                       -> 128
107///   _MSC_VER && __clang__ && !NDEBUG                    -> 128
108///   _MSC_VER                                            -> 512
109///   otherwise                                           -> 1024
110/// ```
111/// `HERMES_LIMIT_STACK_DEPTH` is defined for AddressSanitizer/UBSan builds
112/// (Support/Compiler.h:106-110), because those builds' stack frames are fat
113/// enough that 1024 parser levels outrun the stack.
114///
115/// RUST MAPPING: Rust has no stable `cfg(sanitize = ...)`, so we key off
116/// `debug_assertions`, which lines the two configurations up the way they are
117/// actually used: a DEBUG (unoptimized) Rust build has the same fat frames as
118/// an ASan C++ build — it stack-overflows around 350 parser levels — and the
119/// project's standard oracle (`cmake-build-asan/bin/hermesc`, see CLAUDE.md)
120/// is exactly that ASan build, so debug-vs-ASan is the pairing every
121/// differential gate runs. A RELEASE Rust build gets C++'s release value.
122///
123/// CAVEAT: the corollary is that a RELEASE Rust build differentialed against
124/// the ASan `hermesc` mismatches on deep-nesting inputs (1024 vs 128) — the
125/// tools must be paired by profile. Debug-vs-ASan and release-vs-release both
126/// agree; crossing them does not.
127const MAX_RECURSION_DEPTH: u32 =
128    if cfg!(debug_assertions) { 128 } else { 1024 };
129
130/// RAII guard for the recursion depth counter. Decrements on Drop so that
131/// every `check_recursion` call site is balanced even on early return.
132///
133/// Design note: the C++ uses a macro that increments on entry and decrements
134/// on scope exit via RAII. A guard holding `&mut self.recursion_depth` can't
135/// coexist with the `&mut self` the parse methods need, so instead the counter
136/// lives in an `Rc<Cell<u32>>` and the guard owns its own `Rc` clone (no borrow
137/// into `self`, zero `unsafe`). The `Rc::clone` per recursive entry is a
138/// pointer copy + non-atomic refcount bump — negligible vs. lexing/allocation.
139pub(super) struct RecursionGuard(Rc<Cell<u32>>);
140
141impl Drop for RecursionGuard {
142    fn drop(&mut self) {
143        self.0.set(self.0.get() - 1);
144    }
145}
146
147/// RAII guard for a `paramYield_`/`paramAwait_` flag, restoring the saved old
148/// value on Drop. Mirrors C++ `llvh::SaveAndRestore<bool>` on those fields,
149/// which flip the flag for a scope (name-binding, or args+body) and restore it
150/// on EVERY exit path — including error early-returns. A manual save-local +
151/// restore-at-end would leak the new value on `?` early-returns, so the guard
152/// must own the restore.
153///
154/// Same `Rc<Cell<bool>>` design as `RecursionGuard`: the flag lives in an
155/// `Rc<Cell<bool>>` on the parser, and the guard owns its own `Rc` clone plus
156/// the saved old value, so the caller can freely use `&mut self` while the
157/// guard is alive.
158pub(super) struct ParamFlagGuard {
159    cell: Rc<Cell<bool>>,
160    old: bool,
161}
162
163impl Drop for ParamFlagGuard {
164    fn drop(&mut self) {
165        self.cell.set(self.old);
166    }
167}
168
169/// RAII guard for `jsx_depth`, restoring the saved old value on Drop. Mirrors
170/// the C++ `llvh::SaveAndRestore<uint32_t>(jsxDepth_, <value>)` at
171/// JSParserImpl-jsx.cpp:24, 78, 176, which set a new depth for a scope and
172/// restore the old one on EVERY exit — including the `?`/None error
173/// early-returns. A manual save-local + restore-at-end would leak the new value
174/// on a `?` early-return, so the guard owns the restore.
175///
176/// Same `Rc<Cell<u32>>` design as `RecursionGuard`: the depth lives in an
177/// `Rc<Cell<u32>>` on the parser, and the guard owns its own `Rc` clone plus
178/// the saved old value, so the caller can freely use `&mut self` while the
179/// guard is alive.
180pub(super) struct JsxDepthGuard {
181    cell: Rc<Cell<u32>>,
182    old: u32,
183}
184
185impl Drop for JsxDepthGuard {
186    fn drop(&mut self) {
187        self.cell.set(self.old);
188    }
189}
190
191/// The JS parser.
192///
193/// Four lifetime parameters:
194/// - `'gc`: the borrow-of-lock lifetime. `gc.alloc(n)` returns
195///   `&'gc Node<'gc>` because `GCLock::alloc<'s>(&'s self, ..)` makes
196///   `'s = 'gc`. This is also the node child-ref lifetime (i.e. child refs
197///   inside built nodes are `&'gc Node<'gc>`).
198/// - `'ast`: the `Context`'s own arena lifetime (first type param of
199///   `GCLock<'ast, 'ctx>`). Kept separate so the borrow `'gc` doesn't
200///   accidentally constrain when the `Context` was created.
201/// - `'ctx`: the `&mut Context` borrow lifetime (second type param).
202///   Kept separate for the same reason.
203/// - `'a`: the lexer's borrow lifetimes (`&'a mut SourceErrorManager` and
204///   `&'a AtomTable`).
205///
206/// In practice `'ast`, `'ctx`, and `'a` all unify to the enclosing call
207/// frame, so callers see no extra friction.
208///
209/// Port of `lib/Parser/JSParserImpl.h`/`.cpp`.
210pub struct JSParserImpl<'gc, 'ast, 'ctx, 'a> {
211    /// The arena lock; all nodes are allocated through this.
212    /// `gc.alloc(n: Node<'gc>) -> &'gc Node<'gc>` (borrow lifetime `'gc`).
213    gc: &'gc GCLock<'ast, 'ctx>,
214    /// The lexer driving the token stream. Owns `&'a mut SourceErrorManager`.
215    pub(super) lexer: JSLexer<'a>,
216    /// Current parser recursion depth (stack-overflow guard). In an
217    /// `Rc<Cell<u32>>` so `RecursionGuard` can own a handle without borrowing
218    /// `self` (see `RecursionGuard`).
219    recursion_depth: Rc<Cell<u32>>,
220    /// Set when the parser is inside a generator function (`yield`). In an
221    /// `Rc<Cell<bool>>` so `ParamFlagGuard` can own a handle without borrowing
222    /// `self`, restoring the saved value on every exit path (mirrors the C++
223    /// `llvh::SaveAndRestore<bool>` on `paramYield_`).
224    pub(super) param_yield: Rc<Cell<bool>>,
225    /// Set when the parser is inside an async function (`await`).
226    /// Read in P1.3+ (await expression parsing in parseUnaryExpression).
227    /// In an `Rc<Cell<bool>>` — see `param_yield`.
228    pub(super) param_await: Rc<Cell<bool>>,
229    /// Set on the `use static builtin` directive.
230    pub(super) use_static_builtin: bool,
231    /// Whether an anonymous function type (`T => U` without parentheses) is
232    /// allowed in the current type-annotation context. Port of the C++
233    /// `allowAnonFunctionType_` field (JSParserImpl.h:255).
234    /// In an `Rc<Cell<bool>>` — see `param_yield`.
235    pub(super) allow_anon_function_type: Rc<Cell<bool>>,
236    /// Whether a conditional type (`T extends U ? V : W`) not wrapped in
237    /// parentheses is allowed. Port of the C++ `allowConditionalType_` field
238    /// (JSParserImpl.h:259). In an `Rc<Cell<bool>>` — see `param_yield`.
239    pub(super) allow_conditional_type: Rc<Cell<bool>>,
240    /// Current JSX element nesting depth. Port of the C++ `jsxDepth_` field
241    /// (JSParserImpl.h:251). Controls the lexer-mode switch in
242    /// `parse_jsx_opening_element`/`parse_jsx_closing`: only the outermost
243    /// (`<= 1`) self-closing/closing tag returns to standard JS mode; deeper
244    /// tags stay in JSX-child mode. In an `Rc<Cell<u32>>` so `JsxDepthGuard` can
245    /// own a handle without borrowing `self` (see `JsxDepthGuard`).
246    pub(super) jsx_depth: Rc<Cell<u32>>,
247    /// The current parser mode. Port of `pass_{FullParse}` (JSParserImpl.h:179).
248    pub(super) pass: ParserPass,
249    /// Side-table built during `PreParse` and consumed during `LazyParse`.
250    /// Port of the `PreParsedBufferInfo` pointer held by `JSParserImpl`
251    /// (JSParserImpl.h, used in PreParser.h).
252    pub(super) pre_parsed: PreParsedBufferInfo,
253    /// Whether the current function is an arrow. Only set/restored by
254    /// `SaveFunctionState`. Port of `isArrowFunction_` (JSParserImpl.h:225).
255    pub(super) is_arrow_function: Rc<Cell<bool>>,
256    /// Whether the nearest enclosing non-arrow function contains an arrow.
257    /// Port of `containsArrowFunctions_` (JSParserImpl.h:236).
258    pub(super) contains_arrow_functions: Rc<Cell<bool>>,
259    /// Whether that function may contain an arrow child that references
260    /// `arguments`, requiring eager Arguments capture.
261    /// Port of `mayContainArrowFunctionsUsingArguments_` (JSParserImpl.h:246).
262    pub(super) may_contain_arrow_functions_using_arguments: Rc<Cell<bool>>,
263    /// Directives seen in the current function scope (for lazy directive
264    /// recovery). Port of `seenDirectives_` (JSParserImpl.h:220).
265    pub(super) seen_directives: Vec<Vec<u8>>,
266}
267
268impl<'gc, 'ast, 'ctx, 'a> JSParserImpl<'gc, 'ast, 'ctx, 'a> {
269    /// Construct the parser and lex the first token (C++ ctor does
270    /// `tok_ = lexer_.advance()`).
271    pub fn new(gc: &'gc GCLock<'ast, 'ctx>, mut lexer: JSLexer<'a>) -> Self {
272        // Initialize the lexer's strict mode from the context, mirroring the
273        // C++ JSParserImpl constructor which passes `context.isStrictMode()` to
274        // the JSLexer constructor. The JSLexer's own default is strict=true, but
275        // a default parse (script, no "use strict") must start in sloppy mode so
276        // that e.g. `let;` lexes/parses as a loose-mode identifier expression.
277        lexer.set_strict_mode(gc.ctx().strict_mode());
278        lexer.advance(GrammarContext::AllowRegExp);
279        JSParserImpl {
280            gc,
281            lexer,
282            recursion_depth: Rc::new(Cell::new(0)),
283            param_yield: Rc::new(Cell::new(false)),
284            param_await: Rc::new(Cell::new(false)),
285            use_static_builtin: false,
286            allow_anon_function_type: Rc::new(Cell::new(false)),
287            allow_conditional_type: Rc::new(Cell::new(false)),
288            jsx_depth: Rc::new(Cell::new(0)),
289            pass: ParserPass::FullParse,
290            pre_parsed: PreParsedBufferInfo {
291                function_info: HashMap::new(),
292            },
293            is_arrow_function: Rc::new(Cell::new(false)),
294            contains_arrow_functions: Rc::new(Cell::new(false)),
295            may_contain_arrow_functions_using_arguments: Rc::new(Cell::new(false)),
296            seen_directives: Vec::new(),
297        }
298    }
299
300    /// Construct the parser in a specific pass. Port of the C++
301    /// `JSParserImpl(Context&, bufferId, ParserPass)` ctor (JSParserImpl.cpp:39).
302    pub fn new_with_pass(
303        gc: &'gc GCLock<'ast, 'ctx>,
304        lexer: JSLexer<'a>,
305        pass: ParserPass,
306    ) -> Self {
307        let mut p = Self::new(gc, lexer);
308        p.pass = pass;
309        p
310    }
311
312    /// True if the parser detected `use static builtin`.
313    pub fn get_use_static_builtin(&self) -> bool {
314        self.use_static_builtin
315    }
316
317    /// Move the pre-parsed side-table out of the parser (leaving an empty one
318    /// in its place). Called after a `PreParse` run to hand the table to the
319    /// caller before a subsequent `LazyParse`.
320    pub fn take_pre_parsed(&mut self) -> PreParsedBufferInfo {
321        std::mem::replace(
322            &mut self.pre_parsed,
323            PreParsedBufferInfo {
324                function_info: HashMap::new(),
325            },
326        )
327    }
328
329    /// Install a pre-parsed side-table produced by a prior `PreParse` run.
330    /// Called before a `LazyParse` so the parser can skip already-indexed
331    /// function bodies.
332    pub fn set_pre_parsed(&mut self, t: PreParsedBufferInfo) {
333        self.pre_parsed = t;
334    }
335
336    /// Set the strict-mode flag on the underlying lexer. Mirrors the C++
337    /// `JSParser::setStrictMode` (JSParser.h:66-68) that `HBC.cpp:158` calls
338    /// immediately before `parseLazyFunction` to propagate the function's
339    /// recorded strict mode into the re-parse.
340    pub fn set_strict_mode(&mut self, strict: bool) {
341        self.lexer.set_strict_mode(strict);
342    }
343
344    /// True if Flow type parsing is enabled. Shorthand for the C++
345    /// `context_.getParseFlow()` calls throughout the parser.
346    pub(super) fn parse_flow(&self) -> bool {
347        self.gc.ctx().parse_flow()
348    }
349
350    /// True if the Flow ambiguous-expression grammar is enabled. Shorthand for
351    /// the C++ `context_.getParseFlowAmbiguous()`.
352    pub(super) fn parse_flow_ambiguous(&self) -> bool {
353        self.gc.ctx().parse_flow_ambiguous()
354    }
355
356    /// True if Flow `component`/`hook` syntax is enabled. Shorthand for the C++
357    /// `context_.getParseFlowComponentSyntax()`.
358    pub(super) fn parse_flow_component_syntax(&self) -> bool {
359        self.gc.ctx().parse_flow_component_syntax()
360    }
361
362    /// True if Flow `record` declarations/expressions are enabled. Shorthand for
363    /// the C++ `context_.getParseFlowRecords()`.
364    pub(super) fn parse_flow_records(&self) -> bool {
365        self.gc.ctx().parse_flow_records()
366    }
367
368    /// True if Flow `match` expressions/statements are enabled. Shorthand for
369    /// the C++ `context_.getParseFlowMatch()`.
370    pub(super) fn parse_flow_match(&self) -> bool {
371        self.gc.ctx().parse_flow_match()
372    }
373
374    /// True if TypeScript parsing is enabled. Shorthand for the C++
375    /// `context_.getParseTS()`. Used by `parse_types()`.
376    pub(super) fn parse_ts(&self) -> bool {
377        self.gc.ctx().parse_ts()
378    }
379
380    /// True if JSX parsing is enabled. Shorthand for the C++
381    /// `context_.getParseJSX()`.
382    pub(super) fn parse_jsx(&self) -> bool {
383        self.gc.ctx().parse_jsx()
384    }
385
386    /// True if any type-annotation dialect is enabled. Port of the C++
387    /// `context_.getParseTypes()` (Context.h:504-506).
388    pub(super) fn parse_types(&self) -> bool {
389        self.parse_flow() || self.parse_ts()
390    }
391
392    /// Parse a type annotation in whichever type dialect is enabled. Port of
393    /// the `parseTypeAnnotation` dispatcher (JSParserImpl.h:1209-1222), which
394    /// calls the Flow version under `getParseFlow()` and otherwise falls
395    /// through to TS (`parseTypeAnnotationTS`, which ignores the
396    /// `allow_anon_function_type` argument — it manages the flag itself).
397    pub(in crate::js) fn parse_type_annotation(
398        &mut self,
399        wrapped_start: Option<SMLoc>,
400        allow_anon_function_type: flow::AllowAnonFunctionType,
401    ) -> Option<&'gc Node<'gc>> {
402        debug_assert!(self.parse_flow() || self.parse_ts());
403        // C++ 1214-1216: Flow first if getParseFlow().
404        if self.parse_flow() {
405            return self.parse_type_annotation_flow(
406                wrapped_start,
407                allow_anon_function_type,
408            );
409        }
410        // C++ 1218-1219: otherwise TS.
411        self.parse_type_annotation_ts(wrapped_start)
412    }
413
414    /// Parse a function return type annotation (a type, or a Flow type
415    /// predicate such as `x is T`) in whichever type dialect is enabled. Port
416    /// of the `parseReturnTypeAnnotation` dispatcher
417    /// (JSParserImpl.h:1224-1237).
418    pub(in crate::js) fn parse_return_type_annotation(
419        &mut self,
420        wrapped_start: Option<SMLoc>,
421        allow_anon_function_type: flow::AllowAnonFunctionType,
422    ) -> Option<&'gc Node<'gc>> {
423        debug_assert!(self.parse_flow() || self.parse_ts());
424        // C++ 1229-1232: Flow first if getParseFlow().
425        if self.parse_flow() {
426            return self.parse_return_type_annotation_flow(
427                wrapped_start,
428                allow_anon_function_type,
429            );
430        }
431        // C++ 1233-1234: otherwise TS (parseTypeAnnotationTS).
432        self.parse_type_annotation_ts(wrapped_start)
433    }
434
435    /// Parse a type-argument list (`<A, B>`) in whichever type dialect is
436    /// enabled. Port of the `parseTypeArguments` dispatcher
437    /// (JSParserImpl.h:1240-1248): Flow if `getParseFlow()`, otherwise TS.
438    pub(in crate::js) fn parse_type_arguments(
439        &mut self,
440    ) -> Option<&'gc Node<'gc>> {
441        debug_assert!(self.parse_flow() || self.parse_ts());
442        // C++ 1242-1244: Flow first if getParseFlow().
443        if self.parse_flow() {
444            return self
445                .parse_type_args_flow(crate::lexer::GrammarContext::Type);
446        }
447        // C++ 1245-1246: otherwise TS.
448        self.parse_ts_type_arguments()
449    }
450
451    #[inline]
452    pub(super) fn cur_kind(&self) -> TokenKind {
453        self.lexer.token().kind()
454    }
455    #[inline]
456    pub(super) fn cur_range(&self) -> SMRange {
457        self.lexer.token().source_range()
458    }
459    #[inline]
460    pub(super) fn cur_start(&self) -> SMLoc {
461        self.lexer.token().start_loc()
462    }
463
464    /// True if the current token is `kind`. Port of `check(TokenKind)`.
465    #[inline]
466    pub(super) fn check(&self, kind: TokenKind) -> bool {
467        self.cur_kind() == kind
468    }
469    /// True if the current token is `k1` or `k2`. Port of `check(k1, k2)`.
470    #[inline]
471    pub(super) fn check2(&self, k1: TokenKind, k2: TokenKind) -> bool {
472        let k = self.cur_kind();
473        k == k1 || k == k2
474    }
475    /// True if the current token is any of three kinds.
476    /// Port of `checkN(k1,k2,k3)`.
477    #[inline]
478    pub(super) fn check_n3(
479        &self,
480        k1: TokenKind,
481        k2: TokenKind,
482        k3: TokenKind,
483    ) -> bool {
484        let k = self.cur_kind();
485        k == k1 || k == k2 || k == k3
486    }
487
488    /// True if the current token is any of four kinds.
489    /// Port of `checkN(k1,k2,k3,k4)`.
490    #[inline]
491    pub(super) fn check_n4(
492        &self,
493        k1: TokenKind,
494        k2: TokenKind,
495        k3: TokenKind,
496        k4: TokenKind,
497    ) -> bool {
498        let k = self.cur_kind();
499        k == k1 || k == k2 || k == k3 || k == k4
500    }
501
502    /// Consume the current token, advancing the lexer; return the consumed
503    /// token's range. Port of `JSParserImpl::advance` (C++ returns the PREVIOUS
504    /// token's range — we copy it out before advancing).
505    pub(super) fn advance(&mut self, grammar_context: GrammarContext) -> SMRange {
506        let prev = self.cur_range();
507        self.lexer.advance(grammar_context);
508        prev
509    }
510
511    /// Consume the current token if it is `kind`; return whether it matched.
512    pub(super) fn check_and_eat(
513        &mut self,
514        kind: TokenKind,
515        grammar_context: GrammarContext,
516    ) -> bool {
517        if self.check(kind) {
518            self.advance(grammar_context);
519            true
520        } else {
521            false
522        }
523    }
524
525    /// Report an error at `range`. Routed through the lexer's SourceErrorManager.
526    pub(super) fn error_at(&mut self, range: SMRange, msg: &str) {
527        self.lexer.get_source_mgr_mut().error_at(
528            range.start,
529            Some(range),
530            msg,
531            hermes_support::diag::Subsystem::Parser,
532        );
533    }
534    /// Report an error at the current token. Port of `error(Twine)`.
535    pub(super) fn error_cur(&mut self, msg: &str) {
536        let range = self.cur_range();
537        self.error_at(range, msg);
538    }
539    /// Report an error at a point location, with no highlighted range. Port of
540    /// the C++ `error(SMLoc, Twine)` overload (used e.g. by the Flow object
541    /// type "Explicit inexact syntax" and 'implies' predicate errors).
542    pub(super) fn error_at_loc(&mut self, loc: SMLoc, msg: &str) {
543        self.lexer.get_source_mgr_mut().error_at(
544            loc,
545            None,
546            msg,
547            hermes_support::diag::Subsystem::Parser,
548        );
549    }
550
551    /// Check the current token is `kind`; if not, report an error and return
552    /// false. Port of `need` (JSParserImpl.cpp:228-238) as invoked by the C++
553    /// call sites that pass no hint, i.e. `need(kind, where, nullptr,
554    /// SMLoc{})`. Sites whose C++ counterpart passes a real `what`/`whatLoc`
555    /// use `need_at` instead.
556    ///
557    /// END-STATE INVARIANT (S3 geometry-restoration sweep, verified by full
558    /// call-site audit against JSParserImpl.cpp/-flow.cpp/-ts.cpp/-jsx.cpp):
559    /// there are exactly 7 no-hint (`nullptr, {}`) sites in the whole C++
560    /// parser — flow.cpp:1232, 3462, 4856; jsx.cpp:260; ts.cpp:835 (five
561    /// genuine `need` calls) plus flow.cpp:4775 and jsx.cpp:430 (two direct
562    /// `errorExpected` calls with no `need`/`eat` guard, because their C++
563    /// condition is compound — e.g. `!check(identifier) &&
564    /// !tok_->isResWord()` — not a single-token `check`). This function is
565    /// called at exactly 6 real call sites: the 5 genuine `need` sites plus
566    /// flow.cpp:4775, which behaves identically to a `need(identifier,
567    /// where)` call even though C++ spells it as a manual check +
568    /// `errorExpected` (see `flow/params.rs::parse_type_param_flow`). The
569    /// 7th (jsx.cpp:430) keeps its compound-condition shape and is ported
570    /// via the dedicated `jsx.rs::error_expected_jsx_element_name` wrapper
571    /// (passing `None, None`), not through this function — matching how
572    /// every OTHER compound-condition direct `errorExpected` call in this
573    /// crate routes through `error_expected_msg` rather than `need`/`eat`.
574    /// There are zero no-hint `eat` sites in C++ (see `eat_at`'s doc), so
575    /// there is no plain `eat` counterpart to this function.
576    pub(super) fn need(&mut self, kind: TokenKind, where_: &str) -> bool {
577        if self.check(kind) {
578            return true;
579        }
580        let msg = format!(
581            "'{}' expected{}",
582            crate::token_kinds::token_kind_str(kind),
583            where_
584        );
585        self.error_expected_msg(&msg, None, None);
586        false
587    }
588
589    /// The geometry half of C++ `errorExpected` (JSParserImpl.cpp:201-225).
590    /// `msg` is the already-built message — the token-list + `where` half
591    /// (cpp:180-199), which each caller below formats itself; `what` and
592    /// `what_loc` are C++'s nullable `what` / `whatLoc`.
593    ///
594    /// The diagnostic's primary location is always the CURRENT token's start
595    /// (`cur_start()`, a point — never the token's full range), matching
596    /// `SMLoc errorLoc = tok_->getStartLoc()` (cpp:201).
597    ///
598    /// C++ decodes the two coordinates only when `whatLoc.isValid()`
599    /// (cpp:207-210); otherwise both `SourceCoords` stay default-constructed
600    /// (`bufId == 0`) and `isSameSourceLineAs` — which requires
601    /// `isValid()` on the receiver — returns false. This port spells "not
602    /// provided" as `None` rather than an invalid `SMLoc`, because its
603    /// `SMLoc` has no invalid sentinel at all: `SourceId` wraps a
604    /// `NonZeroU32` (support/src/location.rs:8), so every `SMLoc` names a
605    /// real buffer and `Option<SMLoc>` IS this port's validity convention
606    /// (the same one `SourceErrorManager::error_at` already uses for its
607    /// range). Consequently the C++ `whatCoords.isValid()` guard on the note
608    /// (cpp:223) collapses into the same `Some`.
609    ///
610    /// `find_coords` is the port of `findBufferLineAndLoc`
611    /// (support/src/manager.rs:248-256; C++ SourceErrorManager.cpp:334-342),
612    /// i.e. the *translating* lookup that `errorExpected` calls — not the
613    /// untranslated `find_untranslated_coords`.
614    ///
615    /// The two arms:
616    /// * same line — emit one diagnostic whose underline spans
617    ///   `combineIntoRange(whatLoc, errorLoc)`, so the tildes run from
618    ///   `what_loc` through one past the error point (cpp:212-219).
619    /// * different lines, or no `whatLoc` — emit a bare point-caret error
620    ///   with NO underline, then a `note` carrying `what` at `what_loc` if
621    ///   both were provided (cpp:220-225).
622    pub(super) fn error_expected_msg(
623        &mut self,
624        msg: &str,
625        what: Option<&str>,
626        what_loc: Option<SMLoc>,
627    ) {
628        let err_loc = self.cur_start();
629        // cpp:205-212. `None` short-circuits exactly like the invalid
630        // `whatCoords` does: `isSameSourceLineAs` is false, so we take the
631        // second arm. When the lines match, cpp:212-219 shows both as one
632        // combined range.
633        let range = match what_loc {
634            Some(w) => {
635                let sm = self.lexer.get_source_mgr();
636                if sm
637                    .find_coords(w)
638                    .is_same_source_line_as(&sm.find_coords(err_loc))
639                {
640                    Some(sm.combine_into_range(w, err_loc))
641                } else {
642                    None
643                }
644            }
645            None => None,
646        };
647        let same_line = range.is_some();
648        self.lexer.get_source_mgr_mut().error_at(
649            err_loc,
650            range,
651            msg,
652            hermes_support::diag::Subsystem::Parser,
653        );
654        // cpp:223-224: the note only exists on the different-line arm, and
655        // only when both `what` and a (valid) `whatLoc` were provided.
656        if !same_line {
657            if let (Some(what), Some(w)) = (what, what_loc) {
658                self.lexer.get_source_mgr_mut().note_at(
659                    w,
660                    None,
661                    what,
662                    hermes_support::diag::Subsystem::Parser,
663                );
664            }
665        }
666    }
667
668    /// Like `need`, but for call sites whose C++ `need(kind, where, what,
669    /// whatLoc)` counterpart passes a real `whatLoc`. `what` is C++'s
670    /// nullable hint text, shown as a `note` at `what_loc` when the two
671    /// locations land on different source lines.
672    pub(super) fn need_at(
673        &mut self,
674        kind: TokenKind,
675        where_: &str,
676        what: Option<&str>,
677        what_loc: SMLoc,
678    ) -> bool {
679        if self.check(kind) {
680            return true;
681        }
682        let msg = format!(
683            "'{}' expected{}",
684            crate::token_kinds::token_kind_str(kind),
685            where_
686        );
687        self.error_expected_msg(&msg, what, Some(what_loc));
688        false
689    }
690    /// Report a "'k1' or 'k2' expected{where_}" error at the current token.
691    /// Port of the two-token `errorExpected(k1, k2, where, what, whatLoc)`
692    /// convenience wrapper (JSParserImpl.h:455) which forwards to
693    /// `errorExpected(ArrayRef<TokenKind>(toks, 2), ...)`. The list-rendering
694    /// logic in C++ `errorExpected` (175-195) joins two tokens with " or " and
695    /// appends " expected". `what_loc` is C++'s `whatLoc`, routed through
696    /// `error_expected_msg`, which owns both geometry arms. Every call site
697    /// audited for S1 task 2 passes a real `whatLoc` in C++, so this takes a
698    /// plain `SMLoc`, not an `Option`; `what` stays nullable, mirroring the
699    /// C++ `const char *`.
700    pub(super) fn error_expected2(
701        &mut self,
702        k1: TokenKind,
703        k2: TokenKind,
704        where_: &str,
705        what: Option<&str>,
706        what_loc: SMLoc,
707    ) {
708        let msg = format!(
709            "'{}' or '{}' expected{}",
710            crate::token_kinds::token_kind_str(k1),
711            crate::token_kinds::token_kind_str(k2),
712            where_
713        );
714        self.error_expected_msg(&msg, what, Some(what_loc));
715    }
716
717    /// Report a "'k1', 'k2' or 'k3' expected{where_}" error at the current
718    /// token. Port of the three-token `errorExpected` initializer-list call
719    /// (e.g. the export-type dispatch at JSParserImpl-flow.cpp:2572-2577); the
720    /// C++ list rendering joins all but the last token with ", " and the last
721    /// with " or ". `what`/`what_loc` are C++'s `what`/`whatLoc` (see
722    /// `error_expected2`).
723    pub(super) fn error_expected3(
724        &mut self,
725        k1: TokenKind,
726        k2: TokenKind,
727        k3: TokenKind,
728        where_: &str,
729        what: Option<&str>,
730        what_loc: SMLoc,
731    ) {
732        let msg = format!(
733            "'{}', '{}' or '{}' expected{}",
734            crate::token_kinds::token_kind_str(k1),
735            crate::token_kinds::token_kind_str(k2),
736            crate::token_kinds::token_kind_str(k3),
737            where_
738        );
739        self.error_expected_msg(&msg, what, Some(what_loc));
740    }
741
742    /// Report a "'k1', 'k2', 'k3' or 'k4' expected{where_}" error at the
743    /// current token. Port of the four-token `errorExpected` initializer-list
744    /// call (e.g. the Flow object-type property separator at
745    /// JSParserImpl-flow.cpp:4141-4148); the C++ list rendering joins all but
746    /// the last token with ", " and the last with " or ". `what`/`what_loc`
747    /// are C++'s `what`/`whatLoc` (see `error_expected2`).
748    // The four tokens plus `where`/`what`/`whatLoc` are the C++ signature.
749    #[allow(clippy::too_many_arguments)]
750    pub(super) fn error_expected4(
751        &mut self,
752        k1: TokenKind,
753        k2: TokenKind,
754        k3: TokenKind,
755        k4: TokenKind,
756        where_: &str,
757        what: Option<&str>,
758        what_loc: SMLoc,
759    ) {
760        let msg = format!(
761            "'{}', '{}', '{}' or '{}' expected{}",
762            crate::token_kinds::token_kind_str(k1),
763            crate::token_kinds::token_kind_str(k2),
764            crate::token_kinds::token_kind_str(k3),
765            crate::token_kinds::token_kind_str(k4),
766            where_
767        );
768        self.error_expected_msg(&msg, what, Some(what_loc));
769    }
770
771    /// Check the current token is `kind`; if so consume and return true, else
772    /// report an error and return false. Port of `eat` (JSParserImpl.cpp:
773    /// 240-251). Unlike `need`, EVERY C++ `eat` call site across the whole
774    /// parser (JSParserImpl.cpp/-flow.cpp/-ts.cpp/-jsx.cpp) passes a real
775    /// `whatLoc` — none of the 7 no-hint (`nullptr, {}`) sites is an `eat`
776    /// call — so there is no plain-`eat` counterpart here; every site uses
777    /// `eat_at`. Port of the same `eat`, which simply forwards to `need_at`.
778    pub(super) fn eat_at(
779        &mut self,
780        kind: TokenKind,
781        grammar_context: GrammarContext,
782        where_: &str,
783        what: Option<&str>,
784        what_loc: SMLoc,
785    ) -> bool {
786        if self.need_at(kind, where_, what, what_loc) {
787            self.advance(grammar_context);
788            true
789        } else {
790            false
791        }
792    }
793
794    /// The raw source bytes of the absolute source range `[start, end)`.
795    /// Shared by the call sites that reproduce the C++
796    /// `StringRef(start.getPointer(), end - start)` raw-slice idiom (directive
797    /// raws, literal-type raws).
798    pub(super) fn source_bytes(&self, start: SMLoc, end: SMLoc) -> &[u8] {
799        let buf_start = self.lexer.get_buffer_start();
800        let buf = self.lexer.buffer_bytes();
801        &buf[(start.offset - buf_start) as usize
802            ..(end.offset - buf_start) as usize]
803    }
804
805    /// Intern the raw source text of the absolute source range `[start, end)`.
806    /// The Rust equivalent of the C++
807    /// `lexer_.getStringLiteral(StringRef(start, end - start))` idiom used for
808    /// the raw spelling of literal type annotations.
809    pub(super) fn source_bytes_atom(
810        &self,
811        start: SMLoc,
812        end: SMLoc,
813    ) -> hermes_atom_table::AtomBytes {
814        self.lexer.get_string_literal(self.source_bytes(start, end))
815    }
816
817    /// Increment the recursion depth and return a guard that decrements it on
818    /// drop. Returns `None` (and reports an error) if the limit is exceeded.
819    ///
820    /// Port of the `CHECK_RECURSION` macro (JSParserImpl.h). The returned guard
821    /// owns an `Rc<Cell<u32>>` clone and decrements on drop, so the caller can
822    /// freely use `&mut self` for parse calls while the guard is alive.
823    pub(super) fn check_recursion(&mut self) -> Option<RecursionGuard> {
824        let depth = self.recursion_depth.get() + 1;
825        // `>=`, not `>`: C++ increments first, then `recursionDepthCheck()`
826        // (JSParserImpl.h:699-704) reports the error unless the POST-increment
827        // depth is still `< MAX_RECURSION_DEPTH`. Using `>` here would allow
828        // one extra nesting level and shift every recursion error by one
829        // production.
830        if depth >= MAX_RECURSION_DEPTH {
831            // Don't leave it incremented.
832            // Point location, NOT the token's range: C++
833            // `recursionDepthExceeded` (JSParserImpl.cpp:348-352) calls
834            // `error(tok_->getStartLoc(), ...)`, i.e. the `error(SMLoc,
835            // Twine)` overload (JSParserImpl.h:472-474), which renders a bare
836            // caret. Passing the range would underline the whole token
837            // (`^~~~~` instead of `^`) on any multi-character trip token.
838            let loc = self.cur_start();
839            self.error_at_loc(
840                loc,
841                "Too many nested expressions/statements/declarations",
842            );
843            return None;
844        }
845        self.recursion_depth.set(depth);
846        Some(RecursionGuard(Rc::clone(&self.recursion_depth)))
847    }
848
849    /// Set `param_yield` to `new_val`, returning a guard that restores the old
850    /// value on Drop. Port of `llvh::SaveAndRestore<bool>(paramYield_, new)`.
851    pub(super) fn save_param_yield(&self, new_val: bool) -> ParamFlagGuard {
852        let old = self.param_yield.get();
853        self.param_yield.set(new_val);
854        ParamFlagGuard {
855            cell: Rc::clone(&self.param_yield),
856            old,
857        }
858    }
859
860    /// Set `param_await` to `new_val`, returning a guard that restores the old
861    /// value on Drop. Port of `llvh::SaveAndRestore<bool>(paramAwait_, new)`.
862    pub(super) fn save_param_await(&self, new_val: bool) -> ParamFlagGuard {
863        let old = self.param_await.get();
864        self.param_await.set(new_val);
865        ParamFlagGuard {
866            cell: Rc::clone(&self.param_await),
867            old,
868        }
869    }
870
871    /// Set `allow_anon_function_type` to `new_val`, returning a guard that
872    /// restores the old value on Drop. Port of the
873    /// `llvh::SaveAndRestore<bool>(allowAnonFunctionType_, new)` in
874    /// `parseTypeAnnotationFlow` (JSParserImpl-flow.cpp:3083-3085).
875    pub(super) fn save_allow_anon_function_type(
876        &self,
877        new_val: bool,
878    ) -> ParamFlagGuard {
879        let old = self.allow_anon_function_type.get();
880        self.allow_anon_function_type.set(new_val);
881        ParamFlagGuard {
882            cell: Rc::clone(&self.allow_anon_function_type),
883            old,
884        }
885    }
886
887    /// Set `allow_conditional_type` to `new_val`, returning a guard that
888    /// restores the old value on Drop. Port of the
889    /// `llvh::SaveAndRestore<bool>(allowConditionalType_, ...)` uses in the
890    /// Flow type grammar (e.g. JSParserImpl-flow.cpp:3101).
891    pub(super) fn save_allow_conditional_type(
892        &self,
893        new_val: bool,
894    ) -> ParamFlagGuard {
895        let old = self.allow_conditional_type.get();
896        self.allow_conditional_type.set(new_val);
897        ParamFlagGuard {
898            cell: Rc::clone(&self.allow_conditional_type),
899            old,
900        }
901    }
902
903    /// Set `jsx_depth` to `new_val`, returning a guard that restores the old
904    /// value on Drop. Port of the
905    /// `llvh::SaveAndRestore<uint32_t>(jsxDepth_, <value>)` uses in the JSX
906    /// grammar (JSParserImpl-jsx.cpp:24, 78, 176).
907    pub(super) fn save_jsx_depth(&self, new_val: u32) -> JsxDepthGuard {
908        let old = self.jsx_depth.get();
909        self.jsx_depth.set(new_val);
910        JsxDepthGuard {
911            cell: Rc::clone(&self.jsx_depth),
912            old,
913        }
914    }
915
916    /// Return a placeholder `SMRange` (zero-width at current token start).
917    /// Used as the initial `NodeMetadata` before `set_location` stamps the
918    /// real range, mirroring the C++ pattern of constructing a node then
919    /// calling `setLocation`.
920    pub(super) fn dummy_range(&self) -> SMRange {
921        let loc = self.cur_start();
922        SMRange {
923            start: loc,
924            end: loc,
925        }
926    }
927
928    /// Return an *invalid* `SMRange` (mirrors a C++ default-constructed
929    /// `SMRange()`, whose `isValid()` is false). Used for nodes that C++ builds
930    /// without ever calling `setLocation` — e.g. the fresh `RestElement` created
931    /// from a `SpreadElement` in the async-arrow reparse path. The dumper's
932    /// `range_is_valid` treats `start.offset > end.offset` as invalid, so loc and
933    /// range are omitted, exactly as in the C++ dump.
934    ///
935    /// Contrast [`Self::dummy_range`], a *valid* zero-width placeholder that is
936    /// expected to be overwritten by a subsequent `set_location` call.
937    pub(super) fn invalid_range(&self) -> SMRange {
938        let loc = self.cur_start();
939        SMRange {
940            start: SMLoc {
941                source: loc.source,
942                offset: 1,
943            },
944            end: SMLoc {
945                source: loc.source,
946                offset: 0,
947            },
948        }
949    }
950
951    /// Allocate `node` with its source locations set. Port of the 3-arg
952    /// `setLocation(start, end, node)`: debug loc defaults to start.
953    pub(super) fn set_location(
954        &self,
955        start: SMLoc,
956        end: SMLoc,
957        node: Node<'gc>,
958    ) -> &'gc Node<'gc> {
959        let allocated = self.gc.alloc(node);
960        let md = allocated.metadata();
961        md.range.set(SMRange { start, end });
962        md.debug_loc.set(start);
963        allocated
964    }
965
966    /// Allocate `node` with an explicit debug loc. Port of the 4-arg
967    /// `setLocation(start, end, debugLoc, node)`.
968    ///
969    /// Used where C++ passes a *different* `debugLoc` than `start` — currently
970    /// only the postfix `UpdateExpression` case where `debugLoc` is the start of
971    /// the `++`/`--` operator token while `start` is the start of the operand.
972    pub(super) fn set_location_d(
973        &self,
974        start: SMLoc,
975        end: SMLoc,
976        debug: SMLoc,
977        node: Node<'gc>,
978    ) -> &'gc Node<'gc> {
979        let allocated = self.gc.alloc(node);
980        let md = allocated.metadata();
981        md.range.set(SMRange { start, end });
982        md.debug_loc.set(debug);
983        allocated
984    }
985
986    /// Parse the whole program. Entry point for the parser (and, on the C++
987    /// side, of the PreParse pass too — `JSParserImpl::preParseBuffer`,
988    /// `JSParserImpl.cpp:7539`, calls this same `parse()`; `parseLazyFunction`
989    /// is a separate entry with no such gate and is unaffected).
990    /// Port of `JSParserImpl::parse` (JSParserImpl.cpp:164-172):
991    /// ```cpp
992    /// Optional<ESTree::ProgramNode *> JSParserImpl::parse() {
993    ///   PerfSection parsing("Parsing JavaScript");
994    ///   tok_ = lexer_.advance();
995    ///   auto res = parseProgram();
996    ///   if (!res)
997    ///     return None;
998    ///   if (lexer_.getSourceMgr().getErrorCount() != 0)
999    ///     return None;
1000    ///   return res.getValue();
1001    /// }
1002    /// ```
1003    /// `tok_ = lexer_.advance()` is done by the lexer's own construction
1004    /// (see `Self::new`'s doc); the tail gate is ported here: even when
1005    /// `parseProgram` recovers and returns a tree, a nonzero error count
1006    /// (e.g. a strict-mode octal literal) discards it.
1007    pub fn parse(&mut self) -> Option<&'gc Node<'gc>> {
1008        let res = self.parse_program()?;
1009        if self.lexer.get_source_mgr().error_count() != 0 {
1010            return None;
1011        }
1012        Some(res)
1013    }
1014
1015    /// Parse a `Program` node. Port of `JSParserImpl::parseProgram` (355-373).
1016    ///
1017    /// Parses directives + a statement list until EOF, then wraps in a Program.
1018    fn parse_program(&mut self) -> Option<&'gc Node<'gc>> {
1019        use hermes_ast::node::Program;
1020        use hermes_ast::node_child::{NodeList, NodeMetadata};
1021
1022        let start = self.cur_start();
1023
1024        let mut stmts: Vec<&'gc Node<'gc>> = Vec::new();
1025        if !self.parse_statement_list(
1026            Param::default(),
1027            [TokenKind::eof],
1028            /* parse_directives= */ true,
1029            AllowImportExport::Yes,
1030            &mut stmts,
1031        ) {
1032            return None;
1033        }
1034
1035        let end = if stmts.is_empty() {
1036            start
1037        } else {
1038            stmts.last().unwrap().metadata().range.get().end
1039        };
1040
1041        let body = NodeList::from_iter(self.gc, stmts);
1042        let program = Node::Program(Program::new(
1043            NodeMetadata::new(SMRange { start, end }),
1044            body,
1045        ));
1046        Some(self.set_location(start, end, program))
1047    }
1048
1049    /// Test-only accessor for the current token kind.
1050    #[cfg(test)]
1051    pub(crate) fn cur_kind_pub(&self) -> TokenKind {
1052        self.cur_kind()
1053    }
1054
1055    /// Test-only accessor for the error count.
1056    #[cfg(test)]
1057    pub(crate) fn error_count_pub(&self) -> u32 {
1058        self.lexer.get_source_mgr().error_count()
1059    }
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064    use super::*;
1065
1066    #[test]
1067    fn parser_constructs_and_sees_first_token() {
1068        use hermes_ast::context::Context;
1069        use hermes_support::manager::SourceErrorManager;
1070
1071        let mut sm = SourceErrorManager::new();
1072        let buf_id = sm.add_buffer_bytes("input", b"  /* hi */  ");
1073        let mut ctx = Context::new();
1074        let gc = ctx.lock();
1075        let atoms = &gc.ctx().atom_table;
1076        let lexer = crate::lexer::JSLexer::new(
1077            buf_id,
1078            &mut sm,
1079            atoms,
1080            crate::lexer::GrammarContext::AllowRegExp,
1081        );
1082        let parser = JSParserImpl::new(&gc, lexer);
1083        assert_eq!(
1084            parser.cur_kind_pub(),
1085            crate::token_kinds::TokenKind::eof
1086        );
1087    }
1088
1089    #[test]
1090    fn parses_empty_program() {
1091        use hermes_ast::context::Context;
1092        use hermes_ast::node::Node;
1093        use hermes_support::manager::SourceErrorManager;
1094
1095        let mut sm = SourceErrorManager::new();
1096        let buf_id = sm.add_buffer_bytes("input", b"/* only trivia */\n");
1097        let mut ctx = Context::new();
1098        let gc = ctx.lock();
1099        let atoms = &gc.ctx().atom_table;
1100        let lexer = crate::lexer::JSLexer::new(
1101            buf_id,
1102            &mut sm,
1103            atoms,
1104            crate::lexer::GrammarContext::AllowRegExp,
1105        );
1106        let mut parser = JSParserImpl::new(&gc, lexer);
1107        let program = parser.parse().expect("empty program parses");
1108        match program {
1109            Node::Program(p) => assert!(p.body.is_empty(), "empty source -> empty body"),
1110            other => panic!("expected Program, got {:?}", other.kind()),
1111        }
1112        assert_eq!(parser.error_count_pub(), 0);
1113    }
1114
1115    #[test]
1116    fn parses_numeric_literal_stmt() {
1117        use hermes_ast::context::Context;
1118        use hermes_ast::node::Node;
1119        use hermes_support::manager::SourceErrorManager;
1120
1121        let mut sm = SourceErrorManager::new();
1122        let buf_id = sm.add_buffer_bytes("input", b"42;\n");
1123        let mut ctx = Context::new();
1124        let gc = ctx.lock();
1125        let atoms = &gc.ctx().atom_table;
1126        let lexer = crate::lexer::JSLexer::new(
1127            buf_id,
1128            &mut sm,
1129            atoms,
1130            crate::lexer::GrammarContext::AllowRegExp,
1131        );
1132        let mut parser = JSParserImpl::new(&gc, lexer);
1133        let program = parser.parse().expect("42; parses");
1134        assert_eq!(parser.error_count_pub(), 0);
1135        if let Node::Program(p) = program {
1136            assert_eq!(p.body.iter().count(), 1);
1137            let stmt = p.body.iter().next().unwrap();
1138            if let Node::ExpressionStatement(es) = stmt {
1139                if let Node::NumericLiteral(nl) = es.expression {
1140                    assert_eq!(nl.value.get(), 42.0);
1141                } else {
1142                    panic!("expected NumericLiteral");
1143                }
1144            } else {
1145                panic!("expected ExpressionStatement");
1146            }
1147        } else {
1148            panic!("expected Program");
1149        }
1150    }
1151
1152    /// `parse()`'s tail gate (`JSParserImpl.cpp:168-172`): a RECOVERABLE
1153    /// parse error (the lexer reports it but `parseProgram` still returns a
1154    /// tree) must still make `parse()` return `None`, mirroring
1155    /// `if (lexer_.getSourceMgr().getErrorCount() != 0) return None;`.
1156    /// `"use strict"; var x = 010;` is exactly this shape: `010` is a legacy
1157    /// octal literal, which the lexer rejects under strict mode as a
1158    /// recoverable error (parsing continues) — see
1159    /// `sema_corpus_parser/parse-error-recoverable.js`, the same input used
1160    /// as the end-to-end pin for this gate.
1161    #[test]
1162    fn parse_returns_none_on_recoverable_error() {
1163        use hermes_ast::context::Context;
1164        use hermes_support::manager::SourceErrorManager;
1165
1166        let mut sm = SourceErrorManager::new();
1167        let buf_id =
1168            sm.add_buffer_bytes("input", b"\"use strict\"; var x = 010;\n");
1169        let mut ctx = Context::new();
1170        let gc = ctx.lock();
1171        let atoms = &gc.ctx().atom_table;
1172        let lexer = crate::lexer::JSLexer::new(
1173            buf_id,
1174            &mut sm,
1175            atoms,
1176            crate::lexer::GrammarContext::AllowRegExp,
1177        );
1178        let mut parser = JSParserImpl::new(&gc, lexer);
1179        let program = parser.parse();
1180        assert!(parser.error_count_pub() > 0, "octal literal must error");
1181        assert!(
1182            program.is_none(),
1183            "parse() must return None once the source has errors, even \
1184             though parseProgram() itself recovered and built a tree"
1185        );
1186    }
1187
1188    #[test]
1189    fn parses_empty_statement() {
1190        use hermes_ast::context::Context;
1191        use hermes_ast::node::Node;
1192        use hermes_support::manager::SourceErrorManager;
1193
1194        let mut sm = SourceErrorManager::new();
1195        let buf_id = sm.add_buffer_bytes("input", b";;;\n");
1196        let mut ctx = Context::new();
1197        let gc = ctx.lock();
1198        let atoms = &gc.ctx().atom_table;
1199        let lexer = crate::lexer::JSLexer::new(
1200            buf_id,
1201            &mut sm,
1202            atoms,
1203            crate::lexer::GrammarContext::AllowRegExp,
1204        );
1205        let mut parser = JSParserImpl::new(&gc, lexer);
1206        let program = parser.parse().expect(";;; parses");
1207        assert_eq!(parser.error_count_pub(), 0);
1208        if let Node::Program(p) = program {
1209            assert_eq!(p.body.iter().count(), 3);
1210            for stmt in p.body {
1211                assert!(
1212                    matches!(stmt, Node::EmptyStatement(_)),
1213                    "expected EmptyStatement"
1214                );
1215            }
1216        } else {
1217            panic!("expected Program");
1218        }
1219    }
1220
1221    /// `if(x);` parses cleanly as of P2.4 (was a deferred-error test in P1.1).
1222    #[test]
1223    fn if_statement_parses() {
1224        use hermes_ast::context::Context;
1225        use hermes_ast::node::Node;
1226        use hermes_support::manager::SourceErrorManager;
1227
1228        let mut sm = SourceErrorManager::new();
1229        let buf_id = sm.add_buffer_bytes("input", b"if(x);");
1230        let mut ctx = Context::new();
1231        let gc = ctx.lock();
1232        let atoms = &gc.ctx().atom_table;
1233        let lexer = crate::lexer::JSLexer::new(
1234            buf_id,
1235            &mut sm,
1236            atoms,
1237            crate::lexer::GrammarContext::AllowRegExp,
1238        );
1239        let mut parser = JSParserImpl::new(&gc, lexer);
1240        let program = parser.parse().expect("if statement parses in P2.4");
1241        assert_eq!(parser.error_count_pub(), 0, "zero errors");
1242        let Node::Program(p) = program else {
1243            panic!("expected Program")
1244        };
1245        let stmt = p.body.iter().next().expect("one statement");
1246        assert!(
1247            matches!(stmt, Node::IfStatement(_)),
1248            "expected IfStatement, got {:?}",
1249            stmt.kind()
1250        );
1251    }
1252
1253    /// P3.1: a function expression now parses as a FunctionExpression.
1254    #[test]
1255    fn function_expression_parses() {
1256        use hermes_ast::context::Context;
1257        use hermes_ast::node::Node;
1258        use hermes_support::manager::SourceErrorManager;
1259
1260        let mut sm = SourceErrorManager::new();
1261        let mut ctx = Context::new();
1262        let gc = ctx.lock();
1263        let atoms = &gc.ctx().atom_table;
1264
1265        let expr = parse_expr_from(&gc, &mut sm, atoms, b"(function(){});");
1266        assert!(
1267            matches!(expr, Node::FunctionExpression(_)),
1268            "expected FunctionExpression, got {:?}",
1269            expr.kind()
1270        );
1271    }
1272
1273    /// Helper: parse `src` and assert it fails with at least one error
1274    /// (used for the still-deferred declaration forms).
1275    fn assert_parse_errors(src: &[u8], why: &str) {
1276        use hermes_ast::context::Context;
1277        use hermes_support::manager::SourceErrorManager;
1278
1279        let mut sm = SourceErrorManager::new();
1280        let buf_id = sm.add_buffer_bytes("input", src);
1281        let mut ctx = Context::new();
1282        let gc = ctx.lock();
1283        let atoms = &gc.ctx().atom_table;
1284        let lexer = crate::lexer::JSLexer::new(
1285            buf_id,
1286            &mut sm,
1287            atoms,
1288            crate::lexer::GrammarContext::AllowRegExp,
1289        );
1290        let mut parser = JSParserImpl::new(&gc, lexer);
1291        assert!(parser.parse().is_none(), "{why}");
1292        assert!(parser.error_count_pub() >= 1, "{why}: expected an error");
1293    }
1294
1295    /// Shared body of [`assert_parse_has_errors`] /
1296    /// [`assert_flow_parse_has_errors`]: parse `src` (with Flow parsing
1297    /// enabled iff `parse_flow`) and assert at least one error was reported —
1298    /// the parse may still recover and return a `Program`.
1299    fn assert_parse_has_errors_impl(src: &[u8], why: &str, parse_flow: bool) {
1300        use hermes_ast::context::Context;
1301        use hermes_support::manager::SourceErrorManager;
1302
1303        let mut sm = SourceErrorManager::new();
1304        let buf_id = sm.add_buffer_bytes("input", src);
1305        let mut ctx = Context::new();
1306        ctx.set_parse_flow(parse_flow);
1307        let gc = ctx.lock();
1308        let atoms = &gc.ctx().atom_table;
1309        let lexer = crate::lexer::JSLexer::new(
1310            buf_id,
1311            &mut sm,
1312            atoms,
1313            crate::lexer::GrammarContext::AllowRegExp,
1314        );
1315        let mut parser = JSParserImpl::new(&gc, lexer);
1316        let _ = parser.parse();
1317        assert!(parser.error_count_pub() >= 1, "{why}: expected an error");
1318    }
1319
1320    /// Like [`assert_parse_errors`], but only requires that at least one error
1321    /// was reported — the parse may still recover and return a `Program`. Used
1322    /// for diagnostics that C++ reports but continues past (e.g. a duplicate
1323    /// named import, or an `import` nested in a block).
1324    fn assert_parse_has_errors(src: &[u8], why: &str) {
1325        assert_parse_has_errors_impl(src, why, false);
1326    }
1327
1328    /// P2 capstone: top-level declaration forms that route into
1329    /// `parseDeclaration`/`parseStatementListItem` must emit an HONEST deferral
1330    /// error (not a silent misparse). Functions/classes are P3; import/export
1331    /// are P4.
1332    // P3.1: function declarations/expressions, params, body.
1333
1334    /// Helper: parse `src`, expect zero errors, return the first top-level
1335    /// statement. Shorthand for [`flow_parse_stmt_at`] with index 0.
1336    fn parse_one_stmt<'gc>(
1337        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
1338        sm: &mut hermes_support::manager::SourceErrorManager,
1339        src: &[u8],
1340    ) -> &'gc hermes_ast::node::Node<'gc> {
1341        flow_parse_stmt_at(gc, sm, src, 0)
1342    }
1343
1344    #[test]
1345    fn function_declaration_parses() {
1346        use hermes_ast::context::Context;
1347        use hermes_ast::node::Node;
1348        let mut sm = hermes_support::manager::SourceErrorManager::new();
1349        let mut ctx = Context::new();
1350        let gc = ctx.lock();
1351        let stmt = parse_one_stmt(&gc, &mut sm, b"function f(){}");
1352        assert!(
1353            matches!(stmt, Node::FunctionDeclaration(_)),
1354            "expected FunctionDeclaration, got {:?}",
1355            stmt.kind()
1356        );
1357    }
1358
1359    #[test]
1360    fn generator_declaration_has_generator_flag() {
1361        use hermes_ast::context::Context;
1362        use hermes_ast::node::Node;
1363        let mut sm = hermes_support::manager::SourceErrorManager::new();
1364        let mut ctx = Context::new();
1365        let gc = ctx.lock();
1366        let stmt = parse_one_stmt(&gc, &mut sm, b"function* h(){}");
1367        if let Node::FunctionDeclaration(fd) = stmt {
1368            assert!(fd.generator.get(), "generator flag is true");
1369            assert!(!fd.r#async.get(), "async flag is false");
1370        } else {
1371            panic!("expected FunctionDeclaration");
1372        }
1373    }
1374
1375    #[test]
1376    fn async_declaration_has_async_flag() {
1377        use hermes_ast::context::Context;
1378        use hermes_ast::node::Node;
1379        let mut sm = hermes_support::manager::SourceErrorManager::new();
1380        let mut ctx = Context::new();
1381        let gc = ctx.lock();
1382        let stmt = parse_one_stmt(&gc, &mut sm, b"async function k(){}");
1383        if let Node::FunctionDeclaration(fd) = stmt {
1384            assert!(fd.r#async.get(), "async flag is true");
1385            assert!(!fd.generator.get(), "generator flag is false");
1386        } else {
1387            panic!("expected FunctionDeclaration");
1388        }
1389    }
1390
1391    #[test]
1392    fn function_params_identifier_and_rest() {
1393        use hermes_ast::context::Context;
1394        use hermes_ast::node::Node;
1395        let mut sm = hermes_support::manager::SourceErrorManager::new();
1396        let mut ctx = Context::new();
1397        let gc = ctx.lock();
1398        let stmt = parse_one_stmt(&gc, &mut sm, b"function f(a, ...r){}");
1399        if let Node::FunctionDeclaration(fd) = stmt {
1400            let params: Vec<_> = fd.params.iter().collect();
1401            assert_eq!(params.len(), 2);
1402            assert!(
1403                matches!(params[0], Node::Identifier(_)),
1404                "first param is Identifier"
1405            );
1406            assert!(
1407                matches!(params[1], Node::RestElement(_)),
1408                "second param is RestElement"
1409            );
1410        } else {
1411            panic!("expected FunctionDeclaration");
1412        }
1413    }
1414
1415    #[test]
1416    fn function_params_object_and_array_patterns() {
1417        use hermes_ast::context::Context;
1418        use hermes_ast::node::Node;
1419        let mut sm = hermes_support::manager::SourceErrorManager::new();
1420        let mut ctx = Context::new();
1421        let gc = ctx.lock();
1422        let stmt = parse_one_stmt(&gc, &mut sm, b"function g({x},[y]){}");
1423        if let Node::FunctionDeclaration(fd) = stmt {
1424            let params: Vec<_> = fd.params.iter().collect();
1425            assert_eq!(params.len(), 2);
1426            assert!(
1427                matches!(params[0], Node::ObjectPattern(_)),
1428                "first param is ObjectPattern"
1429            );
1430            assert!(
1431                matches!(params[1], Node::ArrayPattern(_)),
1432                "second param is ArrayPattern"
1433            );
1434        } else {
1435            panic!("expected FunctionDeclaration");
1436        }
1437    }
1438
1439    /// `await` was implemented in P1.3 but only reachable inside an async
1440    /// function body now that function bodies parse (P3.1).
1441    #[test]
1442    fn await_in_async_body_parses() {
1443        let mut sm = hermes_support::manager::SourceErrorManager::new();
1444        assert!(
1445            parse_snippet(&mut sm, b"async function f(){ await x; }"),
1446            "await in async body must parse cleanly"
1447        );
1448    }
1449
1450    /// P3.2: a generator body containing `yield` now parses cleanly.
1451    #[test]
1452    fn yield_in_generator_parses() {
1453        let mut sm = hermes_support::manager::SourceErrorManager::new();
1454        assert!(
1455            parse_snippet(&mut sm, b"function* g(){ yield 1; }"),
1456            "yield in generator body must parse cleanly"
1457        );
1458    }
1459
1460    // ----- P3.6: classes + decorators -----
1461
1462    /// `class A extends B {}` -> ClassDeclaration whose superClass is the
1463    /// identifier `B`.
1464    #[test]
1465    fn class_declaration_with_heritage() {
1466        use hermes_ast::context::Context;
1467        use hermes_ast::node::Node;
1468        let mut sm = hermes_support::manager::SourceErrorManager::new();
1469        let mut ctx = Context::new();
1470        let gc = ctx.lock();
1471        let stmt = parse_one_stmt(&gc, &mut sm, b"class A extends B {}");
1472        let Node::ClassDeclaration(cd) = stmt else {
1473            panic!("expected ClassDeclaration, got {:?}", stmt.kind());
1474        };
1475        let sup = cd.super_class.expect("superClass present");
1476        match sup {
1477            Node::Identifier(id) => {
1478                let bytes = gc.ctx().atom_table.bytes(id.name.get());
1479                assert_eq!(bytes, b"B");
1480            }
1481            other => panic!("expected Identifier superClass, got {:?}", other.kind()),
1482        }
1483    }
1484
1485    /// Helper: parse `class A { <member> }` and return the single class-body
1486    /// element.
1487    fn parse_one_class_member<'gc>(
1488        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
1489        sm: &mut hermes_support::manager::SourceErrorManager,
1490        member_src: &str,
1491    ) -> &'gc hermes_ast::node::Node<'gc> {
1492        use hermes_ast::node::Node;
1493        let src = format!("class A {{ {member_src} }}");
1494        let stmt = parse_one_stmt(gc, sm, src.as_bytes());
1495        let Node::ClassDeclaration(cd) = stmt else {
1496            panic!("expected ClassDeclaration, got {:?}", stmt.kind());
1497        };
1498        let Node::ClassBody(cb) = cd.body else {
1499            panic!("expected ClassBody");
1500        };
1501        cb.body.iter().next().expect("one class member")
1502    }
1503
1504    /// `m(){}` -> MethodDefinition with kind "method".
1505    #[test]
1506    fn class_method_kind_method() {
1507        use hermes_ast::context::Context;
1508        use hermes_ast::node::Node;
1509        let mut sm = hermes_support::manager::SourceErrorManager::new();
1510        let mut ctx = Context::new();
1511        let gc = ctx.lock();
1512        let member = parse_one_class_member(&gc, &mut sm, "m(){}");
1513        let Node::MethodDefinition(md) = member else {
1514            panic!("expected MethodDefinition, got {:?}", member.kind());
1515        };
1516        let kind = gc.ctx().atom_table.bytes(md.kind.get());
1517        assert_eq!(kind, b"method");
1518        assert!(!md.r#static.get(), "not static");
1519    }
1520
1521    /// `constructor(){}` -> MethodDefinition with kind "constructor".
1522    #[test]
1523    fn class_method_kind_constructor() {
1524        use hermes_ast::context::Context;
1525        use hermes_ast::node::Node;
1526        let mut sm = hermes_support::manager::SourceErrorManager::new();
1527        let mut ctx = Context::new();
1528        let gc = ctx.lock();
1529        let member = parse_one_class_member(&gc, &mut sm, "constructor(){}");
1530        let Node::MethodDefinition(md) = member else {
1531            panic!("expected MethodDefinition, got {:?}", member.kind());
1532        };
1533        let kind = gc.ctx().atom_table.bytes(md.kind.get());
1534        assert_eq!(kind, b"constructor");
1535    }
1536
1537    /// `get x(){}` -> MethodDefinition with kind "get".
1538    #[test]
1539    fn class_method_kind_get() {
1540        use hermes_ast::context::Context;
1541        use hermes_ast::node::Node;
1542        let mut sm = hermes_support::manager::SourceErrorManager::new();
1543        let mut ctx = Context::new();
1544        let gc = ctx.lock();
1545        let member = parse_one_class_member(&gc, &mut sm, "get x(){}");
1546        let Node::MethodDefinition(md) = member else {
1547            panic!("expected MethodDefinition, got {:?}", member.kind());
1548        };
1549        let kind = gc.ctx().atom_table.bytes(md.kind.get());
1550        assert_eq!(kind, b"get");
1551    }
1552
1553    /// `static s(){}` -> static MethodDefinition.
1554    #[test]
1555    fn class_method_static() {
1556        use hermes_ast::context::Context;
1557        use hermes_ast::node::Node;
1558        let mut sm = hermes_support::manager::SourceErrorManager::new();
1559        let mut ctx = Context::new();
1560        let gc = ctx.lock();
1561        let member = parse_one_class_member(&gc, &mut sm, "static s(){}");
1562        let Node::MethodDefinition(md) = member else {
1563            panic!("expected MethodDefinition, got {:?}", member.kind());
1564        };
1565        assert!(md.r#static.get(), "static flag set");
1566    }
1567
1568    /// `#p(){}` -> MethodDefinition whose key is a PrivateName.
1569    #[test]
1570    fn class_private_method_key_is_private_name() {
1571        use hermes_ast::context::Context;
1572        use hermes_ast::node::Node;
1573        let mut sm = hermes_support::manager::SourceErrorManager::new();
1574        let mut ctx = Context::new();
1575        let gc = ctx.lock();
1576        let member = parse_one_class_member(&gc, &mut sm, "#p(){}");
1577        let Node::MethodDefinition(md) = member else {
1578            panic!("expected MethodDefinition, got {:?}", member.kind());
1579        };
1580        assert!(
1581            matches!(md.key, Node::PrivateName(_)),
1582            "method key is PrivateName, got {:?}",
1583            md.key.kind()
1584        );
1585    }
1586
1587    /// `x = 1;` -> ClassProperty with a value.
1588    #[test]
1589    fn class_field_with_value() {
1590        use hermes_ast::context::Context;
1591        use hermes_ast::node::Node;
1592        let mut sm = hermes_support::manager::SourceErrorManager::new();
1593        let mut ctx = Context::new();
1594        let gc = ctx.lock();
1595        let member = parse_one_class_member(&gc, &mut sm, "x = 1;");
1596        let Node::ClassProperty(cp) = member else {
1597            panic!("expected ClassProperty, got {:?}", member.kind());
1598        };
1599        assert!(cp.value.is_some(), "field has a value");
1600    }
1601
1602    /// `#f;` -> ClassPrivateProperty.
1603    #[test]
1604    fn class_private_field() {
1605        use hermes_ast::context::Context;
1606        use hermes_ast::node::Node;
1607        let mut sm = hermes_support::manager::SourceErrorManager::new();
1608        let mut ctx = Context::new();
1609        let gc = ctx.lock();
1610        let member = parse_one_class_member(&gc, &mut sm, "#f;");
1611        assert!(
1612            matches!(member, Node::ClassPrivateProperty(_)),
1613            "expected ClassPrivateProperty, got {:?}",
1614            member.kind()
1615        );
1616    }
1617
1618    /// `static { }` -> StaticBlock.
1619    #[test]
1620    fn class_static_block() {
1621        use hermes_ast::context::Context;
1622        use hermes_ast::node::Node;
1623        let mut sm = hermes_support::manager::SourceErrorManager::new();
1624        let mut ctx = Context::new();
1625        let gc = ctx.lock();
1626        let member = parse_one_class_member(&gc, &mut sm, "static { }");
1627        assert!(
1628            matches!(member, Node::StaticBlock(_)),
1629            "expected StaticBlock, got {:?}",
1630            member.kind()
1631        );
1632    }
1633
1634    /// `const C = class {};` -> ClassExpression.
1635    #[test]
1636    fn class_expression_parses() {
1637        use hermes_ast::context::Context;
1638        use hermes_ast::node::Node;
1639        let mut sm = hermes_support::manager::SourceErrorManager::new();
1640        let mut ctx = Context::new();
1641        let gc = ctx.lock();
1642        let atoms = &gc.ctx().atom_table;
1643        let expr = parse_expr_from(&gc, &mut sm, atoms, b"(class {});");
1644        assert!(
1645            matches!(expr, Node::ClassExpression(_)),
1646            "expected ClassExpression, got {:?}",
1647            expr.kind()
1648        );
1649    }
1650
1651    /// A decorated class declaration: `@dec class A {}` -> ClassDeclaration with
1652    /// a single Decorator.
1653    #[test]
1654    fn class_declaration_with_decorator() {
1655        use hermes_ast::context::Context;
1656        use hermes_ast::node::Node;
1657        let mut sm = hermes_support::manager::SourceErrorManager::new();
1658        let mut ctx = Context::new();
1659        let gc = ctx.lock();
1660        let stmt = parse_one_stmt(&gc, &mut sm, b"@dec class A {}");
1661        let Node::ClassDeclaration(cd) = stmt else {
1662            panic!("expected ClassDeclaration, got {:?}", stmt.kind());
1663        };
1664        let decorators: Vec<_> = cd.decorators.iter().collect();
1665        assert_eq!(decorators.len(), 1, "one decorator");
1666        assert!(
1667            matches!(decorators[0], Node::Decorator(_)),
1668            "expected Decorator node"
1669        );
1670    }
1671
1672    /// The class body is always strict mode, but that strictness must NOT leak
1673    /// into the enclosing (sloppy) code. After a class declaration, a `with`
1674    /// statement — which is illegal in strict mode — must still parse cleanly.
1675    #[test]
1676    fn class_strict_mode_does_not_leak() {
1677        use hermes_ast::context::Context;
1678        use hermes_support::manager::SourceErrorManager;
1679
1680        let mut sm = SourceErrorManager::new();
1681        let buf_id = sm.add_buffer_bytes("input", b"class A {}\nwith(x) y;");
1682        let mut ctx = Context::new();
1683        let gc = ctx.lock();
1684        let atoms = &gc.ctx().atom_table;
1685        let lexer = crate::lexer::JSLexer::new(
1686            buf_id,
1687            &mut sm,
1688            atoms,
1689            crate::lexer::GrammarContext::AllowRegExp,
1690        );
1691        let mut parser = JSParserImpl::new(&gc, lexer);
1692        assert!(
1693            parser.parse().is_some(),
1694            "with-statement after class must parse (sloppy mode restored)"
1695        );
1696        assert_eq!(
1697            parser.error_count_pub(),
1698            0,
1699            "no errors: class strict mode must not leak to enclosing sloppy code"
1700        );
1701    }
1702
1703    // P4.2: import declarations are now implemented; see the `import_*` tests
1704    // further below. The `import x from 'm';` form parses cleanly.
1705
1706    // P4.1: `import(...)` and `import.meta` expression forms.
1707
1708    #[test]
1709    fn import_call_no_options() {
1710        use hermes_ast::context::Context;
1711        use hermes_ast::node::Node;
1712        use hermes_support::manager::SourceErrorManager;
1713
1714        let mut sm = SourceErrorManager::new();
1715        let mut ctx = Context::new();
1716        let gc = ctx.lock();
1717        let atoms = &gc.ctx().atom_table;
1718
1719        let expr = parse_expr_from(&gc, &mut sm, atoms, b"import('m');");
1720        if let Node::ImportExpression(ie) = expr {
1721            assert!(
1722                matches!(ie.source, Node::StringLiteral(_)),
1723                "source should be a StringLiteral, got {:?}",
1724                ie.source.kind()
1725            );
1726            assert!(ie.options.is_none(), "options should be None");
1727        } else {
1728            panic!("expected ImportExpression, got {:?}", expr.kind());
1729        }
1730    }
1731
1732    #[test]
1733    fn import_call_with_options() {
1734        use hermes_ast::context::Context;
1735        use hermes_ast::node::Node;
1736        use hermes_support::manager::SourceErrorManager;
1737
1738        let mut sm = SourceErrorManager::new();
1739        let mut ctx = Context::new();
1740        let gc = ctx.lock();
1741        let atoms = &gc.ctx().atom_table;
1742
1743        let expr = parse_expr_from(&gc, &mut sm, atoms, b"import('m', {});");
1744        if let Node::ImportExpression(ie) = expr {
1745            assert!(
1746                matches!(ie.options, Some(Node::ObjectExpression(_))),
1747                "options should be Some(ObjectExpression), got {:?}",
1748                ie.options.map(|o| o.kind())
1749            );
1750        } else {
1751            panic!("expected ImportExpression, got {:?}", expr.kind());
1752        }
1753    }
1754
1755    #[test]
1756    fn import_meta_property() {
1757        use hermes_ast::context::Context;
1758        use hermes_ast::node::Node;
1759        use hermes_support::manager::SourceErrorManager;
1760
1761        let mut sm = SourceErrorManager::new();
1762        let mut ctx = Context::new();
1763        let gc = ctx.lock();
1764        let atoms = &gc.ctx().atom_table;
1765
1766        let expr = parse_expr_from(&gc, &mut sm, atoms, b"import.meta;");
1767        if let Node::MetaProperty(mp) = expr {
1768            if let Node::Identifier(meta) = mp.meta {
1769                assert_eq!(
1770                    gc.ctx().atom_table.bytes(meta.name.get()),
1771                    b"import",
1772                    "meta identifier name should be `import`"
1773                );
1774            } else {
1775                panic!("meta should be an Identifier");
1776            }
1777            if let Node::Identifier(prop) = mp.property {
1778                assert_eq!(
1779                    gc.ctx().atom_table.bytes(prop.name.get()),
1780                    b"meta",
1781                    "property identifier name should be `meta`"
1782                );
1783            } else {
1784                panic!("property should be an Identifier");
1785            }
1786        } else {
1787            panic!("expected MetaProperty, got {:?}", expr.kind());
1788        }
1789    }
1790
1791    #[test]
1792    fn import_meta_bad_form_errors() {
1793        assert_parse_errors(b"import.foo;", "'meta' expected after import.");
1794    }
1795
1796    /// C++ uses `check(metaIdent_)` (escape-insensitive) for the `meta`
1797    /// keyword, so an escaped `meta` is still a valid `import.meta`
1798    /// MetaProperty — it must NOT trip the `'meta' expected` error path.
1799    #[test]
1800    fn import_meta_escaped_meta_parses() {
1801        use hermes_ast::context::Context;
1802        use hermes_ast::node::Node;
1803        use hermes_support::manager::SourceErrorManager;
1804
1805        let mut sm = SourceErrorManager::new();
1806        let mut ctx = Context::new();
1807        let gc = ctx.lock();
1808        let atoms = &gc.ctx().atom_table;
1809
1810        let expr =
1811            parse_expr_from(&gc, &mut sm, atoms, b"import.m\\u0065ta;");
1812        if let Node::MetaProperty(mp) = expr {
1813            if let Node::Identifier(prop) = mp.property {
1814                assert_eq!(
1815                    gc.ctx().atom_table.bytes(prop.name.get()),
1816                    b"meta",
1817                    "escaped `m\\u0065ta` should intern to `meta`"
1818                );
1819            } else {
1820                panic!("property should be an Identifier");
1821            }
1822        } else {
1823            panic!("expected MetaProperty, got {:?}", expr.kind());
1824        }
1825    }
1826
1827    // P4.2: import declarations.
1828
1829    /// Helper: the interned bytes of an `Identifier` node.
1830    fn ident_bytes<'gc>(
1831        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
1832        node: &hermes_ast::node::Node<'gc>,
1833    ) -> Vec<u8> {
1834        if let hermes_ast::node::Node::Identifier(id) = node {
1835            gc.ctx().atom_table.bytes(id.name.get()).to_vec()
1836        } else {
1837            panic!("expected Identifier, got {:?}", node.kind());
1838        }
1839    }
1840
1841    #[test]
1842    fn import_default_specifier_parses() {
1843        use hermes_ast::context::Context;
1844        use hermes_ast::node::Node;
1845        let mut sm = hermes_support::manager::SourceErrorManager::new();
1846        let mut ctx = Context::new();
1847        let gc = ctx.lock();
1848        let stmt = parse_one_stmt(&gc, &mut sm, b"import x from 'm';");
1849        if let Node::ImportDeclaration(decl) = stmt {
1850            assert_eq!(decl.specifiers.iter().count(), 1);
1851            let spec = decl.specifiers.iter().next().unwrap();
1852            if let Node::ImportDefaultSpecifier(ds) = spec {
1853                assert_eq!(ident_bytes(&gc, ds.local), b"x");
1854            } else {
1855                panic!("expected ImportDefaultSpecifier, got {:?}", spec.kind());
1856            }
1857            if let Node::StringLiteral(sl) = decl.source {
1858                assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
1859            } else {
1860                panic!("source should be a StringLiteral");
1861            }
1862            assert_eq!(
1863                gc.ctx().atom_table.bytes(decl.import_kind.get()),
1864                b"value"
1865            );
1866        } else {
1867            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1868        }
1869    }
1870
1871    #[test]
1872    fn import_named_specifier_parses() {
1873        use hermes_ast::context::Context;
1874        use hermes_ast::node::Node;
1875        let mut sm = hermes_support::manager::SourceErrorManager::new();
1876        let mut ctx = Context::new();
1877        let gc = ctx.lock();
1878        let stmt = parse_one_stmt(&gc, &mut sm, b"import {b as c} from 'm';");
1879        if let Node::ImportDeclaration(decl) = stmt {
1880            assert_eq!(decl.specifiers.iter().count(), 1);
1881            let spec = decl.specifiers.iter().next().unwrap();
1882            if let Node::ImportSpecifier(is) = spec {
1883                assert_eq!(ident_bytes(&gc, is.imported), b"b");
1884                assert_eq!(ident_bytes(&gc, is.local), b"c");
1885            } else {
1886                panic!("expected ImportSpecifier, got {:?}", spec.kind());
1887            }
1888        } else {
1889            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1890        }
1891    }
1892
1893    #[test]
1894    fn import_namespace_specifier_parses() {
1895        use hermes_ast::context::Context;
1896        use hermes_ast::node::Node;
1897        let mut sm = hermes_support::manager::SourceErrorManager::new();
1898        let mut ctx = Context::new();
1899        let gc = ctx.lock();
1900        let stmt = parse_one_stmt(&gc, &mut sm, b"import * as ns from 'm';");
1901        if let Node::ImportDeclaration(decl) = stmt {
1902            assert_eq!(decl.specifiers.iter().count(), 1);
1903            let spec = decl.specifiers.iter().next().unwrap();
1904            if let Node::ImportNamespaceSpecifier(ns) = spec {
1905                assert_eq!(ident_bytes(&gc, ns.local), b"ns");
1906            } else {
1907                panic!(
1908                    "expected ImportNamespaceSpecifier, got {:?}",
1909                    spec.kind()
1910                );
1911            }
1912        } else {
1913            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1914        }
1915    }
1916
1917    #[test]
1918    fn import_default_plus_named_parses() {
1919        use hermes_ast::context::Context;
1920        use hermes_ast::node::Node;
1921        let mut sm = hermes_support::manager::SourceErrorManager::new();
1922        let mut ctx = Context::new();
1923        let gc = ctx.lock();
1924        let stmt =
1925            parse_one_stmt(&gc, &mut sm, b"import d, {a, b} from 'm';");
1926        if let Node::ImportDeclaration(decl) = stmt {
1927            let specs: Vec<_> = decl.specifiers.iter().collect();
1928            assert_eq!(specs.len(), 3);
1929            assert!(matches!(specs[0], Node::ImportDefaultSpecifier(_)));
1930            assert!(matches!(specs[1], Node::ImportSpecifier(_)));
1931            assert!(matches!(specs[2], Node::ImportSpecifier(_)));
1932        } else {
1933            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1934        }
1935    }
1936
1937    #[test]
1938    fn import_default_plus_namespace_parses() {
1939        use hermes_ast::context::Context;
1940        use hermes_ast::node::Node;
1941        let mut sm = hermes_support::manager::SourceErrorManager::new();
1942        let mut ctx = Context::new();
1943        let gc = ctx.lock();
1944        let stmt =
1945            parse_one_stmt(&gc, &mut sm, b"import d, * as ns from 'm';");
1946        if let Node::ImportDeclaration(decl) = stmt {
1947            let specs: Vec<_> = decl.specifiers.iter().collect();
1948            assert_eq!(specs.len(), 2);
1949            assert!(matches!(specs[0], Node::ImportDefaultSpecifier(_)));
1950            assert!(matches!(specs[1], Node::ImportNamespaceSpecifier(_)));
1951        } else {
1952            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1953        }
1954    }
1955
1956    #[test]
1957    fn import_bare_parses() {
1958        use hermes_ast::context::Context;
1959        use hermes_ast::node::Node;
1960        let mut sm = hermes_support::manager::SourceErrorManager::new();
1961        let mut ctx = Context::new();
1962        let gc = ctx.lock();
1963        let stmt = parse_one_stmt(&gc, &mut sm, b"import 'm';");
1964        if let Node::ImportDeclaration(decl) = stmt {
1965            assert_eq!(decl.specifiers.iter().count(), 0);
1966            assert_eq!(decl.attributes.iter().count(), 0);
1967            if let Node::StringLiteral(sl) = decl.source {
1968                assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
1969            } else {
1970                panic!("source should be a StringLiteral");
1971            }
1972        } else {
1973            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
1974        }
1975    }
1976
1977    #[test]
1978    fn import_attribute_parses() {
1979        use hermes_ast::context::Context;
1980        use hermes_ast::node::Node;
1981        let mut sm = hermes_support::manager::SourceErrorManager::new();
1982        let mut ctx = Context::new();
1983        let gc = ctx.lock();
1984        let stmt = parse_one_stmt(
1985            &gc,
1986            &mut sm,
1987            b"import x from 'm' with { type: 'json' };",
1988        );
1989        if let Node::ImportDeclaration(decl) = stmt {
1990            assert_eq!(decl.attributes.iter().count(), 1);
1991            let attr = decl.attributes.iter().next().unwrap();
1992            if let Node::ImportAttribute(ia) = attr {
1993                assert_eq!(ident_bytes(&gc, ia.key), b"type");
1994                if let Node::StringLiteral(sl) = ia.value {
1995                    assert_eq!(
1996                        gc.ctx().atom_table.bytes(sl.value.get()),
1997                        b"json"
1998                    );
1999                } else {
2000                    panic!("attribute value should be a StringLiteral");
2001                }
2002            } else {
2003                panic!("expected ImportAttribute, got {:?}", attr.kind());
2004            }
2005        } else {
2006            panic!("expected ImportDeclaration, got {:?}", stmt.kind());
2007        }
2008    }
2009
2010    #[test]
2011    fn import_duplicate_named_errors() {
2012        assert_parse_has_errors(
2013            b"import {a, a} from 'm';",
2014            "duplicate named import is a Duplicate entry error",
2015        );
2016    }
2017
2018    #[test]
2019    fn import_in_block_errors() {
2020        // A `{ import ... }` block body reaches `parse_statement_list_item`
2021        // with `AllowImportExport::No`, triggering the top-level error.
2022        assert_parse_has_errors(
2023            b"{ import x from 'm'; }",
2024            "import inside a block must be at top level of module",
2025        );
2026    }
2027
2028    // P4.3: export declarations.
2029
2030    #[test]
2031    fn export_named_specifier_parses() {
2032        use hermes_ast::context::Context;
2033        use hermes_ast::node::Node;
2034        let mut sm = hermes_support::manager::SourceErrorManager::new();
2035        let mut ctx = Context::new();
2036        let gc = ctx.lock();
2037        // The `var a;` declaration and the `export` share one program.
2038        let buf_id = sm.add_buffer_bytes("input", b"var a;\nexport {a as b};");
2039        let atoms = &gc.ctx().atom_table;
2040        let lexer = crate::lexer::JSLexer::new(
2041            buf_id,
2042            &mut sm,
2043            atoms,
2044            crate::lexer::GrammarContext::AllowRegExp,
2045        );
2046        let mut parser = JSParserImpl::new(&gc, lexer);
2047        let program = parser.parse().expect("parse succeeded");
2048        assert_eq!(parser.error_count_pub(), 0, "zero errors");
2049        let Node::Program(p) = program else {
2050            panic!("expected Program")
2051        };
2052        let stmt = p.body.iter().nth(1).expect("has second statement");
2053        if let Node::ExportNamedDeclaration(decl) = stmt {
2054            assert!(decl.declaration.is_none(), "declaration None");
2055            assert!(decl.source.is_none(), "source None");
2056            assert_eq!(
2057                gc.ctx().atom_table.bytes(decl.export_kind.get()),
2058                b"value"
2059            );
2060            assert_eq!(decl.specifiers.iter().count(), 1);
2061            let spec = decl.specifiers.iter().next().unwrap();
2062            if let Node::ExportSpecifier(es) = spec {
2063                assert_eq!(ident_bytes(&gc, es.exported), b"b");
2064                assert_eq!(ident_bytes(&gc, es.local), b"a");
2065            } else {
2066                panic!("expected ExportSpecifier, got {:?}", spec.kind());
2067            }
2068        } else {
2069            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
2070        }
2071    }
2072
2073    #[test]
2074    fn export_named_from_parses() {
2075        use hermes_ast::context::Context;
2076        use hermes_ast::node::Node;
2077        let mut sm = hermes_support::manager::SourceErrorManager::new();
2078        let mut ctx = Context::new();
2079        let gc = ctx.lock();
2080        let stmt = parse_one_stmt(&gc, &mut sm, b"export {a} from 'm';");
2081        if let Node::ExportNamedDeclaration(decl) = stmt {
2082            if let Some(Node::StringLiteral(sl)) = decl.source {
2083                assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
2084            } else {
2085                panic!("source should be a StringLiteral");
2086            }
2087        } else {
2088            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
2089        }
2090    }
2091
2092    #[test]
2093    fn export_all_parses() {
2094        use hermes_ast::context::Context;
2095        use hermes_ast::node::Node;
2096        let mut sm = hermes_support::manager::SourceErrorManager::new();
2097        let mut ctx = Context::new();
2098        let gc = ctx.lock();
2099        let stmt = parse_one_stmt(&gc, &mut sm, b"export * from 'm';");
2100        if let Node::ExportAllDeclaration(decl) = stmt {
2101            if let Node::StringLiteral(sl) = decl.source {
2102                assert_eq!(gc.ctx().atom_table.bytes(sl.value.get()), b"m");
2103            } else {
2104                panic!("source should be a StringLiteral");
2105            }
2106        } else {
2107            panic!("expected ExportAllDeclaration, got {:?}", stmt.kind());
2108        }
2109    }
2110
2111    #[test]
2112    fn export_namespace_parses() {
2113        use hermes_ast::context::Context;
2114        use hermes_ast::node::Node;
2115        let mut sm = hermes_support::manager::SourceErrorManager::new();
2116        let mut ctx = Context::new();
2117        let gc = ctx.lock();
2118        let stmt = parse_one_stmt(&gc, &mut sm, b"export * as ns from 'm';");
2119        if let Node::ExportNamedDeclaration(decl) = stmt {
2120            assert_eq!(decl.specifiers.iter().count(), 1);
2121            let spec = decl.specifiers.iter().next().unwrap();
2122            if let Node::ExportNamespaceSpecifier(ns) = spec {
2123                assert_eq!(ident_bytes(&gc, ns.exported), b"ns");
2124            } else {
2125                panic!("expected ExportNamespaceSpecifier, got {:?}", spec.kind());
2126            }
2127        } else {
2128            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
2129        }
2130    }
2131
2132    #[test]
2133    fn export_default_expr_parses() {
2134        use hermes_ast::context::Context;
2135        use hermes_ast::node::Node;
2136        let mut sm = hermes_support::manager::SourceErrorManager::new();
2137        let mut ctx = Context::new();
2138        let gc = ctx.lock();
2139        let stmt = parse_one_stmt(&gc, &mut sm, b"export default 1;");
2140        if let Node::ExportDefaultDeclaration(decl) = stmt {
2141            assert!(
2142                matches!(decl.declaration, Node::NumericLiteral(_)),
2143                "declaration should be a NumericLiteral, got {:?}",
2144                decl.declaration.kind()
2145            );
2146        } else {
2147            panic!("expected ExportDefaultDeclaration, got {:?}", stmt.kind());
2148        }
2149    }
2150
2151    #[test]
2152    fn export_default_function_parses() {
2153        use hermes_ast::context::Context;
2154        use hermes_ast::node::Node;
2155        let mut sm = hermes_support::manager::SourceErrorManager::new();
2156        let mut ctx = Context::new();
2157        let gc = ctx.lock();
2158        let stmt = parse_one_stmt(&gc, &mut sm, b"export default function(){}");
2159        if let Node::ExportDefaultDeclaration(decl) = stmt {
2160            if let Node::FunctionDeclaration(fd) = decl.declaration {
2161                assert!(fd.id.is_none(), "default function has no id");
2162            } else {
2163                panic!(
2164                    "declaration should be a FunctionDeclaration, got {:?}",
2165                    decl.declaration.kind()
2166                );
2167            }
2168        } else {
2169            panic!("expected ExportDefaultDeclaration, got {:?}", stmt.kind());
2170        }
2171    }
2172
2173    #[test]
2174    fn export_var_declaration_parses() {
2175        use hermes_ast::context::Context;
2176        use hermes_ast::node::Node;
2177        let mut sm = hermes_support::manager::SourceErrorManager::new();
2178        let mut ctx = Context::new();
2179        let gc = ctx.lock();
2180        let stmt = parse_one_stmt(&gc, &mut sm, b"export var x = 1;");
2181        if let Node::ExportNamedDeclaration(decl) = stmt {
2182            assert!(
2183                matches!(decl.declaration, Some(Node::VariableDeclaration(_))),
2184                "declaration should be a VariableDeclaration, got {:?}",
2185                decl.declaration.map(|d| d.kind())
2186            );
2187        } else {
2188            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
2189        }
2190    }
2191
2192    #[test]
2193    fn export_function_declaration_parses() {
2194        use hermes_ast::context::Context;
2195        use hermes_ast::node::Node;
2196        let mut sm = hermes_support::manager::SourceErrorManager::new();
2197        let mut ctx = Context::new();
2198        let gc = ctx.lock();
2199        let stmt = parse_one_stmt(&gc, &mut sm, b"export function f(){}");
2200        if let Node::ExportNamedDeclaration(decl) = stmt {
2201            assert!(
2202                matches!(decl.declaration, Some(Node::FunctionDeclaration(_))),
2203                "declaration should be a FunctionDeclaration, got {:?}",
2204                decl.declaration.map(|d| d.kind())
2205            );
2206        } else {
2207            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind());
2208        }
2209    }
2210
2211    #[test]
2212    fn export_in_block_errors() {
2213        // A `{ export ... }` block body reaches `parse_statement_list_item`
2214        // with `AllowImportExport::No`. Unlike import, export does NOT push the
2215        // declaration; it just reports the "must be at top level" error.
2216        assert_parse_has_errors(
2217            b"{ export var x = 1; }",
2218            "export inside a block must be at top level of module",
2219        );
2220    }
2221
2222    // P5 capstone: Flow `export type` and export-kind detection
2223    // (C++ JSParserImpl.cpp:7133-7137, 7361-7368; flow.cpp:2499-2576).
2224
2225    /// Helper: parse `src` with Flow enabled, expect one top-level
2226    /// `ExportNamedDeclaration`, and assert its `exportKind` atom is `kind`.
2227    fn assert_flow_export_kind(src: &[u8], kind: &[u8]) {
2228        use hermes_ast::context::Context;
2229        use hermes_ast::node::Node;
2230        let mut sm = hermes_support::manager::SourceErrorManager::new();
2231        let mut ctx = Context::new();
2232        ctx.set_parse_flow(true);
2233        let gc = ctx.lock();
2234        let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
2235        let Node::ExportNamedDeclaration(decl) = stmt else {
2236            panic!("expected ExportNamedDeclaration, got {:?}", stmt.kind())
2237        };
2238        assert_eq!(
2239            gc.ctx().atom_table.bytes(decl.export_kind.get()),
2240            kind,
2241            "exportKind for {:?}",
2242            String::from_utf8_lossy(src)
2243        );
2244    }
2245
2246    /// `export type A = ...;` routes through
2247    /// `parse_export_type_declaration_flow` (C++ 7133-7137 →
2248    /// flow.cpp:2558-2567) and gets exportKind `type`.
2249    #[test]
2250    fn flow_export_type_alias_kind_is_type() {
2251        assert_flow_export_kind(b"export type A = number;", b"type");
2252    }
2253
2254    /// `export opaque type` goes through the `export <Declaration>` path; the
2255    /// kind detection (C++ 7361-7368) makes it `type`.
2256    #[test]
2257    fn flow_export_opaque_type_kind_is_type() {
2258        assert_flow_export_kind(b"export opaque type B = string;", b"type");
2259    }
2260
2261    /// `export interface` goes through the `export <Declaration>` path; the
2262    /// kind detection (C++ 7361-7368) makes it `type`.
2263    #[test]
2264    fn flow_export_interface_kind_is_type() {
2265        assert_flow_export_kind(b"export interface I { x: number }", b"type");
2266    }
2267
2268    /// Value declarations keep exportKind `value` even with Flow enabled.
2269    #[test]
2270    fn flow_export_value_kinds_stay_value() {
2271        assert_flow_export_kind(b"export var x = 1;", b"value");
2272        assert_flow_export_kind(b"export function f(){}", b"value");
2273    }
2274
2275    /// Without `parse_flow`, `export type A = 1;` does not hit the Flow
2276    /// route: `type` is not a declaration start, so it errors exactly like
2277    /// hermesc without `-parse-flow` ("expected declaration in export").
2278    #[test]
2279    fn export_type_without_flow_errors() {
2280        assert_parse_has_errors(
2281            b"export type A = 1;",
2282            "export type without Flow is not a declaration",
2283        );
2284    }
2285
2286    /// The `export type {…}` / `export type *` specifier/re-export forms of
2287    /// parseExportTypeDeclarationFlow (flow.cpp:2504-2557) are ported in P6.6
2288    /// and carry exportKind `type`.
2289    #[test]
2290    fn flow_export_type_clause_and_star_have_type_kind() {
2291        use hermes_ast::context::Context;
2292        use hermes_ast::node::Node;
2293        assert_flow_export_kind(b"export type {x};", b"type");
2294        assert_flow_export_kind(b"export type {x} from 'm';", b"type");
2295
2296        // `export type *` produces an ExportAllDeclaration (not a named one).
2297        let mut sm = hermes_support::manager::SourceErrorManager::new();
2298        let mut ctx = Context::new();
2299        ctx.set_parse_flow(true);
2300        let gc = ctx.lock();
2301        let stmt =
2302            flow_parse_stmt_at(&gc, &mut sm, b"export type * from 'm';", 0);
2303        let Node::ExportAllDeclaration(decl) = stmt else {
2304            panic!("expected ExportAllDeclaration, got {:?}", stmt.kind())
2305        };
2306        assert_eq!(
2307            gc.ctx().atom_table.bytes(decl.export_kind.get()),
2308            b"type"
2309        );
2310    }
2311
2312    /// Array literals are implemented in P1.7; `[1]` must now parse cleanly.
2313    #[test]
2314    fn array_literal_parses() {
2315        use hermes_ast::context::Context;
2316        use hermes_support::manager::SourceErrorManager;
2317
2318        let mut sm = SourceErrorManager::new();
2319        let buf_id = sm.add_buffer_bytes("input", b"[1];");
2320        let mut ctx = Context::new();
2321        let gc = ctx.lock();
2322        let atoms = &gc.ctx().atom_table;
2323        let lexer = crate::lexer::JSLexer::new(
2324            buf_id,
2325            &mut sm,
2326            atoms,
2327            crate::lexer::GrammarContext::AllowRegExp,
2328        );
2329        let mut parser = JSParserImpl::new(&gc, lexer);
2330        assert!(
2331            parser.parse().is_some(),
2332            "array literal should parse successfully in P1.7"
2333        );
2334        assert_eq!(parser.error_count_pub(), 0);
2335    }
2336
2337    #[test]
2338    fn parses_sequence_expression() {
2339        use hermes_ast::context::Context;
2340        use hermes_ast::node::Node;
2341        use hermes_support::manager::SourceErrorManager;
2342
2343        let mut sm = SourceErrorManager::new();
2344        let buf_id = sm.add_buffer_bytes("input", b"1, 2, 3;");
2345        let mut ctx = Context::new();
2346        let gc = ctx.lock();
2347        let atoms = &gc.ctx().atom_table;
2348        let lexer = crate::lexer::JSLexer::new(
2349            buf_id,
2350            &mut sm,
2351            atoms,
2352            crate::lexer::GrammarContext::AllowRegExp,
2353        );
2354        let mut parser = JSParserImpl::new(&gc, lexer);
2355        let program = parser.parse().expect("1, 2, 3; parses");
2356        assert_eq!(parser.error_count_pub(), 0);
2357        if let Node::Program(p) = program {
2358            assert_eq!(p.body.iter().count(), 1);
2359            let stmt = p.body.iter().next().unwrap();
2360            if let Node::ExpressionStatement(es) = stmt {
2361                assert!(
2362                    matches!(es.expression, Node::SequenceExpression(_)),
2363                    "expected SequenceExpression"
2364                );
2365            } else {
2366                panic!("expected ExpressionStatement");
2367            }
2368        }
2369    }
2370
2371    #[test]
2372    fn use_strict_directive_sets_strict_mode() {
2373        use hermes_ast::context::Context;
2374        use hermes_ast::node::Node;
2375        use hermes_support::manager::SourceErrorManager;
2376
2377        let mut sm = SourceErrorManager::new();
2378        let buf_id = sm.add_buffer_bytes("input", b"\"use strict\"; 1;");
2379        let mut ctx = Context::new();
2380        let gc = ctx.lock();
2381        let atoms = &gc.ctx().atom_table;
2382        let lexer = crate::lexer::JSLexer::new(
2383            buf_id,
2384            &mut sm,
2385            atoms,
2386            crate::lexer::GrammarContext::AllowRegExp,
2387        );
2388        let mut parser = JSParserImpl::new(&gc, lexer);
2389        let program = parser.parse().expect("\"use strict\"; 1; parses");
2390        assert_eq!(parser.error_count_pub(), 0);
2391        if let Node::Program(p) = program {
2392            // Body should have 2 statements: the directive + numeric stmt.
2393            assert_eq!(p.body.iter().count(), 2);
2394        }
2395        // Strict mode is now set on the lexer.
2396        assert!(parser.lexer.is_strict_mode());
2397    }
2398
2399    // P1.5: assignment expression tests.
2400
2401    /// Helper: parse a snippet and extract the expression from the first
2402    /// ExpressionStatement.
2403    fn parse_expr_from<'gc>(
2404        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
2405        sm: &mut hermes_support::manager::SourceErrorManager,
2406        atoms: &hermes_atom_table::AtomTable,
2407        src: &[u8],
2408    ) -> &'gc hermes_ast::node::Node<'gc> {
2409        let buf_id = sm.add_buffer_bytes("input", src);
2410        let lexer = crate::lexer::JSLexer::new(
2411            buf_id,
2412            sm,
2413            atoms,
2414            crate::lexer::GrammarContext::AllowRegExp,
2415        );
2416        let mut parser = JSParserImpl::new(gc, lexer);
2417        let program = parser.parse().expect("parse succeeded");
2418        assert_eq!(parser.error_count_pub(), 0, "zero errors");
2419        if let hermes_ast::node::Node::Program(p) = program {
2420            let stmt = p.body.iter().next().expect("has statement");
2421            if let hermes_ast::node::Node::ExpressionStatement(es) = stmt {
2422                return es.expression;
2423            }
2424        }
2425        panic!("expected ExpressionStatement");
2426    }
2427
2428    #[test]
2429    fn parses_simple_assignment() {
2430        use hermes_ast::context::Context;
2431        use hermes_ast::node::Node;
2432        use hermes_support::manager::SourceErrorManager;
2433
2434        let mut sm = SourceErrorManager::new();
2435        let mut ctx = Context::new();
2436        let gc = ctx.lock();
2437        let atoms = &gc.ctx().atom_table;
2438
2439        let expr = parse_expr_from(&gc, &mut sm, atoms, b"a = b;");
2440        match expr {
2441            Node::AssignmentExpression(n) => {
2442                let op_bytes = gc.ctx().atom_table.bytes(n.operator.get());
2443                assert_eq!(op_bytes, b"=", "operator is =");
2444                assert!(
2445                    matches!(n.left, Node::Identifier(_)),
2446                    "left is Identifier"
2447                );
2448                assert!(
2449                    matches!(n.right, Node::Identifier(_)),
2450                    "right is Identifier"
2451                );
2452            }
2453            other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
2454        }
2455    }
2456
2457    #[test]
2458    fn parses_compound_assignment_plus() {
2459        use hermes_ast::context::Context;
2460        use hermes_ast::node::Node;
2461        use hermes_support::manager::SourceErrorManager;
2462
2463        let mut sm = SourceErrorManager::new();
2464        let mut ctx = Context::new();
2465        let gc = ctx.lock();
2466        let atoms = &gc.ctx().atom_table;
2467
2468        let expr = parse_expr_from(&gc, &mut sm, atoms, b"a += 1;");
2469        match expr {
2470            Node::AssignmentExpression(n) => {
2471                let op_bytes = gc.ctx().atom_table.bytes(n.operator.get());
2472                assert_eq!(op_bytes, b"+=", "operator is +=");
2473            }
2474            other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
2475        }
2476    }
2477
2478    #[test]
2479    fn parses_right_assoc_chain() {
2480        // a = b = c  must parse as  a = (b = c)
2481        use hermes_ast::context::Context;
2482        use hermes_ast::node::Node;
2483        use hermes_support::manager::SourceErrorManager;
2484
2485        let mut sm = SourceErrorManager::new();
2486        let mut ctx = Context::new();
2487        let gc = ctx.lock();
2488        let atoms = &gc.ctx().atom_table;
2489
2490        let expr = parse_expr_from(&gc, &mut sm, atoms, b"a = b = c;");
2491        match expr {
2492            Node::AssignmentExpression(outer) => {
2493                // outer.left == a
2494                assert!(
2495                    matches!(outer.left, Node::Identifier(_)),
2496                    "outer.left is Identifier(a)"
2497                );
2498                // outer.right == (b = c)
2499                match outer.right {
2500                    Node::AssignmentExpression(inner) => {
2501                        let inner_left = match inner.left {
2502                            Node::Identifier(id) => id,
2503                            other => panic!(
2504                                "expected Identifier(b), got {:?}",
2505                                other.kind()
2506                            ),
2507                        };
2508                        let b_bytes = gc.ctx().atom_table.bytes(inner_left.name.get());
2509                        assert_eq!(b_bytes, b"b", "inner.left is b");
2510                        assert!(
2511                            matches!(inner.right, Node::Identifier(_)),
2512                            "inner.right is Identifier(c)"
2513                        );
2514                    }
2515                    other => panic!(
2516                        "outer.right must be AssignmentExpression(b=c), got {:?}",
2517                        other.kind()
2518                    ),
2519                }
2520            }
2521            other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
2522        }
2523    }
2524
2525    #[test]
2526    fn assignment_not_confused_with_equality() {
2527        // `a == b` must NOT produce an AssignmentExpression.
2528        use hermes_ast::context::Context;
2529        use hermes_ast::node::Node;
2530        use hermes_support::manager::SourceErrorManager;
2531
2532        let mut sm = SourceErrorManager::new();
2533        let mut ctx = Context::new();
2534        let gc = ctx.lock();
2535        let atoms = &gc.ctx().atom_table;
2536
2537        let expr = parse_expr_from(&gc, &mut sm, atoms, b"a == b;");
2538        assert!(
2539            matches!(expr, Node::BinaryExpression(_)),
2540            "== produces BinaryExpression, not AssignmentExpression"
2541        );
2542    }
2543
2544    #[test]
2545    fn arrow_expr_parses_after_p33() {
2546        // Arrow functions landed in P3.3; `a => b` now parses cleanly.
2547        use hermes_ast::context::Context;
2548        use hermes_support::manager::SourceErrorManager;
2549
2550        let mut sm = SourceErrorManager::new();
2551        let buf_id = sm.add_buffer_bytes("input", b"a => b;");
2552        let mut ctx = Context::new();
2553        let gc = ctx.lock();
2554        let atoms = &gc.ctx().atom_table;
2555        let lexer = crate::lexer::JSLexer::new(
2556            buf_id,
2557            &mut sm,
2558            atoms,
2559            crate::lexer::GrammarContext::AllowRegExp,
2560        );
2561        let mut parser = JSParserImpl::new(&gc, lexer);
2562        assert!(parser.parse().is_some(), "arrow should parse in P3.3");
2563        assert_eq!(parser.error_count_pub(), 0, "no errors");
2564    }
2565
2566    // P1.8: object literal tests.
2567
2568    /// Helper: parse a snippet, return the parse result (Some = success).
2569    fn parse_snippet(sm: &mut hermes_support::manager::SourceErrorManager, src: &[u8]) -> bool {
2570        use hermes_ast::context::Context;
2571        let buf_id = sm.add_buffer_bytes("input", src);
2572        let mut ctx = Context::new();
2573        let gc = ctx.lock();
2574        let atoms = &gc.ctx().atom_table;
2575        let lexer = crate::lexer::JSLexer::new(
2576            buf_id,
2577            sm,
2578            atoms,
2579            crate::lexer::GrammarContext::AllowRegExp,
2580        );
2581        let mut parser = JSParserImpl::new(&gc, lexer);
2582        let result = parser.parse();
2583        result.is_some() && parser.error_count_pub() == 0
2584    }
2585
2586    #[test]
2587    fn object_literal_empty_parses() {
2588        let mut sm = hermes_support::manager::SourceErrorManager::new();
2589        assert!(parse_snippet(&mut sm, b"({});"), "empty object literal");
2590    }
2591
2592    #[test]
2593    fn object_literal_keyed_parses() {
2594        let mut sm = hermes_support::manager::SourceErrorManager::new();
2595        assert!(parse_snippet(&mut sm, b"({a: 1, b: 2});"), "keyed properties");
2596    }
2597
2598    #[test]
2599    fn object_literal_shorthand_parses() {
2600        let mut sm = hermes_support::manager::SourceErrorManager::new();
2601        assert!(parse_snippet(&mut sm, b"({a, b});"), "shorthand properties");
2602    }
2603
2604    #[test]
2605    fn object_literal_computed_parses() {
2606        let mut sm = hermes_support::manager::SourceErrorManager::new();
2607        assert!(parse_snippet(&mut sm, b"({[x]: 1});"), "computed key");
2608    }
2609
2610    #[test]
2611    fn object_literal_spread_parses() {
2612        let mut sm = hermes_support::manager::SourceErrorManager::new();
2613        assert!(parse_snippet(&mut sm, b"({...a});"), "spread element");
2614    }
2615
2616    #[test]
2617    fn object_literal_string_and_number_keys_parse() {
2618        let mut sm = hermes_support::manager::SourceErrorManager::new();
2619        assert!(
2620            parse_snippet(&mut sm, b"({\"s\": 1, 0: 2});"),
2621            "string and number keys"
2622        );
2623    }
2624
2625    #[test]
2626    fn object_literal_get_set_as_data_property() {
2627        // `get` and `set` used as plain property names — must succeed.
2628        let mut sm = hermes_support::manager::SourceErrorManager::new();
2629        assert!(
2630            parse_snippet(&mut sm, b"({get: 1, set: 2});"),
2631            "get/set as data properties"
2632        );
2633        assert!(
2634            parse_snippet(&mut sm, b"({get, set});"),
2635            "get/set shorthand"
2636        );
2637    }
2638
2639    #[test]
2640    fn object_literal_async_as_data_property() {
2641        // `async` used as a plain property name — must succeed.
2642        let mut sm = hermes_support::manager::SourceErrorManager::new();
2643        assert!(
2644            parse_snippet(&mut sm, b"({async: 1});"),
2645            "async as data property"
2646        );
2647        assert!(
2648            parse_snippet(&mut sm, b"({async});"),
2649            "async shorthand"
2650        );
2651    }
2652
2653    #[test]
2654    fn object_literal_cover_initializer_parses() {
2655        // `({a=1})` is a CoverInitializedName — hermesc accepts it in raw AST dump.
2656        let mut sm = hermes_support::manager::SourceErrorManager::new();
2657        assert!(
2658            parse_snippet(&mut sm, b"({a=1});"),
2659            "CoverInitializedName must parse"
2660        );
2661    }
2662
2663    // P3.4: object method / getter / setter tests.
2664
2665    /// Helper: parse `(OBJECT);`, expect success, return the single Property of
2666    /// the contained ObjectExpression.
2667    fn parse_single_property<'gc>(
2668        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
2669        sm: &mut hermes_support::manager::SourceErrorManager,
2670        src: &[u8],
2671    ) -> &'gc hermes_ast::node::Property<'gc> {
2672        use hermes_ast::node::Node;
2673        let expr = parse_expr_ok(gc, sm, src);
2674        let Node::ObjectExpression(obj) = expr else {
2675            panic!("expected ObjectExpression, got {:?}", expr.kind());
2676        };
2677        let props: Vec<_> = obj.properties.iter().collect();
2678        assert_eq!(props.len(), 1, "expected exactly one property");
2679        match props[0] {
2680            Node::Property(p) => p,
2681            other => panic!("expected Property, got {:?}", other.kind()),
2682        }
2683    }
2684
2685    #[test]
2686    fn object_getter_parses() {
2687        // `{get x() { return 1; }}` → Property kind "get", value FunctionExpression.
2688        use hermes_ast::context::Context;
2689        use hermes_ast::node::Node;
2690        let mut sm = hermes_support::manager::SourceErrorManager::new();
2691        let mut ctx = Context::new();
2692        let gc = ctx.lock();
2693        let p = parse_single_property(&gc, &mut sm, b"({get x() { return 1; }});");
2694        assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"get");
2695        assert!(!p.method.get(), "getter is not a method");
2696        assert!(!p.computed.get());
2697        let Node::FunctionExpression(f) = p.value else {
2698            panic!("getter value must be FunctionExpression");
2699        };
2700        assert_eq!(f.params.iter().count(), 0, "getter has no params");
2701        assert!(!f.generator.get());
2702        assert!(!f.r#async.get());
2703    }
2704
2705    #[test]
2706    fn object_setter_parses() {
2707        // `{set x(v) {}}` → Property kind "set", value FunctionExpression w/ 1 param.
2708        use hermes_ast::context::Context;
2709        use hermes_ast::node::Node;
2710        let mut sm = hermes_support::manager::SourceErrorManager::new();
2711        let mut ctx = Context::new();
2712        let gc = ctx.lock();
2713        let p = parse_single_property(&gc, &mut sm, b"({set x(v) {}});");
2714        assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"set");
2715        assert!(!p.method.get(), "setter is not a method");
2716        let Node::FunctionExpression(f) = p.value else {
2717            panic!("setter value must be FunctionExpression");
2718        };
2719        assert_eq!(f.params.iter().count(), 1, "setter has one param");
2720    }
2721
2722    #[test]
2723    fn object_method_parses() {
2724        // `{m() {}}` → Property kind "init", method=true, value FunctionExpression.
2725        use hermes_ast::context::Context;
2726        use hermes_ast::node::Node;
2727        let mut sm = hermes_support::manager::SourceErrorManager::new();
2728        let mut ctx = Context::new();
2729        let gc = ctx.lock();
2730        let p = parse_single_property(&gc, &mut sm, b"({m() {}});");
2731        assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"init");
2732        assert!(p.method.get(), "plain method has method=true");
2733        assert!(!p.shorthand.get());
2734        let Node::FunctionExpression(f) = p.value else {
2735            panic!("method value must be FunctionExpression");
2736        };
2737        assert!(!f.generator.get());
2738        assert!(!f.r#async.get());
2739    }
2740
2741    #[test]
2742    fn object_generator_method_parses() {
2743        // `{*g() {}}` → method=true, value FunctionExpression.generator==true.
2744        use hermes_ast::context::Context;
2745        use hermes_ast::node::Node;
2746        let mut sm = hermes_support::manager::SourceErrorManager::new();
2747        let mut ctx = Context::new();
2748        let gc = ctx.lock();
2749        let p = parse_single_property(&gc, &mut sm, b"({*g() {}});");
2750        assert!(p.method.get());
2751        let Node::FunctionExpression(f) = p.value else {
2752            panic!("generator method value must be FunctionExpression");
2753        };
2754        assert!(f.generator.get(), "generator==true");
2755        assert!(!f.r#async.get());
2756    }
2757
2758    #[test]
2759    fn object_async_method_parses() {
2760        // `{async a() {}}` → method=true, value FunctionExpression.async==true.
2761        use hermes_ast::context::Context;
2762        use hermes_ast::node::Node;
2763        let mut sm = hermes_support::manager::SourceErrorManager::new();
2764        let mut ctx = Context::new();
2765        let gc = ctx.lock();
2766        let p = parse_single_property(&gc, &mut sm, b"({async a() {}});");
2767        assert!(p.method.get());
2768        let Node::FunctionExpression(f) = p.value else {
2769            panic!("async method value must be FunctionExpression");
2770        };
2771        assert!(f.r#async.get(), "async==true");
2772        assert!(!f.generator.get());
2773    }
2774
2775    #[test]
2776    fn object_async_generator_method_parses() {
2777        // `{async *ag() {}}` → both async and generator true.
2778        use hermes_ast::context::Context;
2779        use hermes_ast::node::Node;
2780        let mut sm = hermes_support::manager::SourceErrorManager::new();
2781        let mut ctx = Context::new();
2782        let gc = ctx.lock();
2783        let p = parse_single_property(&gc, &mut sm, b"({async *ag() {}});");
2784        assert!(p.method.get());
2785        let Node::FunctionExpression(f) = p.value else {
2786            panic!("async generator method value must be FunctionExpression");
2787        };
2788        assert!(f.r#async.get(), "async==true");
2789        assert!(f.generator.get(), "generator==true");
2790    }
2791
2792    #[test]
2793    fn object_computed_method_parses() {
2794        // `{[k]() {}}` → computed=true, method=true.
2795        use hermes_ast::context::Context;
2796        use hermes_ast::node::Node;
2797        let mut sm = hermes_support::manager::SourceErrorManager::new();
2798        let mut ctx = Context::new();
2799        let gc = ctx.lock();
2800        let p = parse_single_property(&gc, &mut sm, b"({[k]() {}});");
2801        assert!(p.computed.get(), "computed key");
2802        assert!(p.method.get());
2803        assert!(matches!(p.value, Node::FunctionExpression(_)));
2804    }
2805
2806    #[test]
2807    fn object_string_and_numeric_methods_parse() {
2808        // `{'s'() {}, 0() {}}` → both methods parse with method=true.
2809        let mut sm = hermes_support::manager::SourceErrorManager::new();
2810        assert!(
2811            parse_snippet(&mut sm, b"({'s'() {}, 0() {}});"),
2812            "string- and numeric-keyed methods"
2813        );
2814    }
2815
2816    // P1.8b: destructuring-assignment reparse tests.
2817
2818    /// Helper: parse source, expect success, return first-statement expression.
2819    fn parse_expr_ok<'gc>(
2820        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
2821        sm: &mut hermes_support::manager::SourceErrorManager,
2822        src: &[u8],
2823    ) -> &'gc hermes_ast::node::Node<'gc> {
2824        let buf_id = sm.add_buffer_bytes("input", src);
2825        let atoms = &gc.ctx().atom_table;
2826        let lexer = crate::lexer::JSLexer::new(
2827            buf_id,
2828            sm,
2829            atoms,
2830            crate::lexer::GrammarContext::AllowRegExp,
2831        );
2832        let mut parser = JSParserImpl::new(gc, lexer);
2833        let program = parser.parse().expect("parse succeeded");
2834        assert_eq!(parser.error_count_pub(), 0, "zero errors");
2835        if let hermes_ast::node::Node::Program(p) = program {
2836            let stmt = p.body.iter().next().expect("has statement");
2837            if let hermes_ast::node::Node::ExpressionStatement(es) = stmt {
2838                return es.expression;
2839            }
2840        }
2841        panic!("expected ExpressionStatement");
2842    }
2843
2844    #[test]
2845    fn array_destructure_simple() {
2846        // `[a] = b` → AssignmentExpression(=, ArrayPattern([Identifier(a)]), ...)
2847        use hermes_ast::context::Context;
2848        use hermes_ast::node::Node;
2849        let mut sm = hermes_support::manager::SourceErrorManager::new();
2850        let mut ctx = Context::new();
2851        let gc = ctx.lock();
2852        let expr = parse_expr_ok(&gc, &mut sm, b"[a] = b;");
2853        match expr {
2854            Node::AssignmentExpression(asn) => {
2855                let op = gc.ctx().atom_table.bytes(asn.operator.get());
2856                assert_eq!(op, b"=");
2857                assert!(
2858                    matches!(asn.left, Node::ArrayPattern(_)),
2859                    "left is ArrayPattern, got {:?}",
2860                    asn.left.kind()
2861                );
2862            }
2863            other => panic!("expected AssignmentExpression, got {:?}", other.kind()),
2864        }
2865    }
2866
2867    #[test]
2868    fn array_destructure_with_rest() {
2869        // `[a, ...b] = c` → ArrayPattern contains RestElement.
2870        use hermes_ast::context::Context;
2871        use hermes_ast::node::Node;
2872        let mut sm = hermes_support::manager::SourceErrorManager::new();
2873        let mut ctx = Context::new();
2874        let gc = ctx.lock();
2875        let expr = parse_expr_ok(&gc, &mut sm, b"[a, ...b] = c;");
2876        if let Node::AssignmentExpression(asn) = expr {
2877            if let Node::ArrayPattern(ap) = asn.left {
2878                let elems: Vec<_> = ap.elements.iter().collect();
2879                assert_eq!(elems.len(), 2);
2880                assert!(matches!(elems[0], Node::Identifier(_)));
2881                assert!(matches!(elems[1], Node::RestElement(_)));
2882            } else {
2883                panic!("left must be ArrayPattern");
2884            }
2885        } else {
2886            panic!("expected AssignmentExpression");
2887        }
2888    }
2889
2890    #[test]
2891    fn array_destructure_with_hole() {
2892        // `[a, , b] = c` → ArrayPattern has Empty hole.
2893        use hermes_ast::context::Context;
2894        use hermes_ast::node::Node;
2895        let mut sm = hermes_support::manager::SourceErrorManager::new();
2896        let mut ctx = Context::new();
2897        let gc = ctx.lock();
2898        let expr = parse_expr_ok(&gc, &mut sm, b"[a, , b] = c;");
2899        if let Node::AssignmentExpression(asn) = expr {
2900            if let Node::ArrayPattern(ap) = asn.left {
2901                let elems: Vec<_> = ap.elements.iter().collect();
2902                assert_eq!(elems.len(), 3);
2903                assert!(matches!(elems[0], Node::Identifier(_)));
2904                assert!(matches!(elems[1], Node::Empty(_)));
2905                assert!(matches!(elems[2], Node::Identifier(_)));
2906            } else {
2907                panic!("left must be ArrayPattern");
2908            }
2909        } else {
2910            panic!("expected AssignmentExpression");
2911        }
2912    }
2913
2914    #[test]
2915    fn array_destructure_with_default() {
2916        // `[a = 1, b] = c` → first element is AssignmentPattern.
2917        use hermes_ast::context::Context;
2918        use hermes_ast::node::Node;
2919        let mut sm = hermes_support::manager::SourceErrorManager::new();
2920        let mut ctx = Context::new();
2921        let gc = ctx.lock();
2922        let expr = parse_expr_ok(&gc, &mut sm, b"[a = 1, b] = c;");
2923        if let Node::AssignmentExpression(asn) = expr {
2924            if let Node::ArrayPattern(ap) = asn.left {
2925                let elems: Vec<_> = ap.elements.iter().collect();
2926                assert_eq!(elems.len(), 2);
2927                assert!(
2928                    matches!(elems[0], Node::AssignmentPattern(_)),
2929                    "first element is AssignmentPattern"
2930                );
2931                assert!(matches!(elems[1], Node::Identifier(_)));
2932            } else {
2933                panic!("left must be ArrayPattern");
2934            }
2935        } else {
2936            panic!("expected AssignmentExpression");
2937        }
2938    }
2939
2940    #[test]
2941    fn object_destructure_shorthand() {
2942        // `({a} = b)` → AssignmentExpression(=, ObjectPattern([Property(...)]), ...)
2943        use hermes_ast::context::Context;
2944        use hermes_ast::node::Node;
2945        let mut sm = hermes_support::manager::SourceErrorManager::new();
2946        let mut ctx = Context::new();
2947        let gc = ctx.lock();
2948        let expr = parse_expr_ok(&gc, &mut sm, b"({a} = b);");
2949        if let Node::AssignmentExpression(asn) = expr {
2950            let op = gc.ctx().atom_table.bytes(asn.operator.get());
2951            assert_eq!(op, b"=");
2952            assert!(
2953                matches!(asn.left, Node::ObjectPattern(_)),
2954                "left is ObjectPattern, got {:?}",
2955                asn.left.kind()
2956            );
2957        } else {
2958            panic!("expected AssignmentExpression");
2959        }
2960    }
2961
2962    #[test]
2963    fn object_destructure_cover_initializer() {
2964        // `({a = 1} = b)` → Property value is AssignmentPattern.
2965        use hermes_ast::context::Context;
2966        use hermes_ast::node::Node;
2967        let mut sm = hermes_support::manager::SourceErrorManager::new();
2968        let mut ctx = Context::new();
2969        let gc = ctx.lock();
2970        let expr = parse_expr_ok(&gc, &mut sm, b"({a = 1} = b);");
2971        if let Node::AssignmentExpression(asn) = expr {
2972            if let Node::ObjectPattern(op) = asn.left {
2973                let props: Vec<_> = op.properties.iter().collect();
2974                assert_eq!(props.len(), 1);
2975                if let Node::Property(p) = props[0] {
2976                    assert!(
2977                        matches!(p.value, Node::AssignmentPattern(_)),
2978                        "property value is AssignmentPattern"
2979                    );
2980                } else {
2981                    panic!("expected Property");
2982                }
2983            } else {
2984                panic!("left must be ObjectPattern");
2985            }
2986        } else {
2987            panic!("expected AssignmentExpression");
2988        }
2989    }
2990
2991    #[test]
2992    fn object_destructure_with_rest() {
2993        // `({...r} = o)` → ObjectPattern([RestElement(Identifier(r))]).
2994        use hermes_ast::context::Context;
2995        use hermes_ast::node::Node;
2996        let mut sm = hermes_support::manager::SourceErrorManager::new();
2997        let mut ctx = Context::new();
2998        let gc = ctx.lock();
2999        let expr = parse_expr_ok(&gc, &mut sm, b"({...r} = o);");
3000        if let Node::AssignmentExpression(asn) = expr {
3001            if let Node::ObjectPattern(op) = asn.left {
3002                let props: Vec<_> = op.properties.iter().collect();
3003                assert_eq!(props.len(), 1);
3004                assert!(
3005                    matches!(props[0], Node::RestElement(_)),
3006                    "property is RestElement"
3007                );
3008            } else {
3009                panic!("left must be ObjectPattern");
3010            }
3011        } else {
3012            panic!("expected AssignmentExpression");
3013        }
3014    }
3015
3016    #[test]
3017    fn nested_array_object_destructure() {
3018        // `[{a}, [b]] = c` — nested pattern.
3019        use hermes_ast::context::Context;
3020        use hermes_ast::node::Node;
3021        let mut sm = hermes_support::manager::SourceErrorManager::new();
3022        let mut ctx = Context::new();
3023        let gc = ctx.lock();
3024        let expr = parse_expr_ok(&gc, &mut sm, b"[{a}, [b]] = c;");
3025        if let Node::AssignmentExpression(asn) = expr {
3026            if let Node::ArrayPattern(ap) = asn.left {
3027                let elems: Vec<_> = ap.elements.iter().collect();
3028                assert_eq!(elems.len(), 2);
3029                assert!(matches!(elems[0], Node::ObjectPattern(_)));
3030                assert!(matches!(elems[1], Node::ArrayPattern(_)));
3031            } else {
3032                panic!("left must be ArrayPattern");
3033            }
3034        } else {
3035            panic!("expected AssignmentExpression");
3036        }
3037    }
3038
3039    // P2.1: simple statements + labelled statements.
3040
3041    /// Top-level `return x;` is an illegal location for `return` (not in a
3042    /// function), so it reports the "'return' not in a function" error, but
3043    /// `parseProgram` itself still keeps parsing and produces a valid
3044    /// Program (called directly here, bypassing `parse()`'s tail gate, to
3045    /// exercise the recovery in isolation — the gate itself, which turns
3046    /// this same recoverable error into `parse()` returning `None`, is
3047    /// `parse_returns_none_on_recoverable_error`, above).
3048    #[test]
3049    fn return_outside_function_reports_error_but_parses() {
3050        use hermes_ast::context::Context;
3051        use hermes_ast::node::Node;
3052        let mut sm = hermes_support::manager::SourceErrorManager::new();
3053        let buf_id = sm.add_buffer_bytes("input", b"return x;\n");
3054        let mut ctx = Context::new();
3055        let gc = ctx.lock();
3056        let atoms = &gc.ctx().atom_table;
3057        let lexer = crate::lexer::JSLexer::new(
3058            buf_id,
3059            &mut sm,
3060            atoms,
3061            crate::lexer::GrammarContext::AllowRegExp,
3062        );
3063        let mut parser = JSParserImpl::new(&gc, lexer);
3064        let program = parser.parse_program().expect("return still parses");
3065        assert!(
3066            parser.error_count_pub() >= 1,
3067            "top-level return reports an error"
3068        );
3069        if let Node::Program(p) = program {
3070            let stmt = p.body.iter().next().expect("has statement");
3071            assert!(
3072                matches!(stmt, Node::ReturnStatement(_)),
3073                "still produces a ReturnStatement"
3074            );
3075        } else {
3076            panic!("expected Program");
3077        }
3078    }
3079
3080    /// `throw` with the argument on the next line is a syntax error
3081    /// ("'throw' argument must be on the same line") and the parse fails.
3082    #[test]
3083    fn throw_newline_before_argument_fails() {
3084        use hermes_ast::context::Context;
3085        let mut sm = hermes_support::manager::SourceErrorManager::new();
3086        let buf_id = sm.add_buffer_bytes("input", b"throw\nx;\n");
3087        let mut ctx = Context::new();
3088        let gc = ctx.lock();
3089        let atoms = &gc.ctx().atom_table;
3090        let lexer = crate::lexer::JSLexer::new(
3091            buf_id,
3092            &mut sm,
3093            atoms,
3094            crate::lexer::GrammarContext::AllowRegExp,
3095        );
3096        let mut parser = JSParserImpl::new(&gc, lexer);
3097        assert!(
3098            parser.parse().is_none(),
3099            "throw with newline before argument fails"
3100        );
3101        assert!(parser.error_count_pub() >= 1);
3102    }
3103
3104    /// `foo: x;` parses to a LabeledStatement whose label is `foo` and whose
3105    /// body is the expression statement `x;`.
3106    #[test]
3107    fn labelled_statement_parses() {
3108        use hermes_ast::context::Context;
3109        use hermes_ast::node::Node;
3110        let mut sm = hermes_support::manager::SourceErrorManager::new();
3111        let buf_id = sm.add_buffer_bytes("input", b"foo: x;\n");
3112        let mut ctx = Context::new();
3113        let gc = ctx.lock();
3114        let atoms = &gc.ctx().atom_table;
3115        let lexer = crate::lexer::JSLexer::new(
3116            buf_id,
3117            &mut sm,
3118            atoms,
3119            crate::lexer::GrammarContext::AllowRegExp,
3120        );
3121        let mut parser = JSParserImpl::new(&gc, lexer);
3122        let program = parser.parse().expect("labelled statement parses");
3123        assert_eq!(parser.error_count_pub(), 0, "zero errors");
3124        if let Node::Program(p) = program {
3125            let stmt = p.body.iter().next().expect("has statement");
3126            if let Node::LabeledStatement(ls) = stmt {
3127                if let Node::Identifier(id) = ls.label {
3128                    assert_eq!(gc.ctx().atom_table.bytes(id.name.get()), b"foo");
3129                } else {
3130                    panic!("label must be an Identifier");
3131                }
3132                assert!(
3133                    matches!(ls.body, Node::ExpressionStatement(_)),
3134                    "body is an ExpressionStatement"
3135                );
3136            } else {
3137                panic!("expected LabeledStatement");
3138            }
3139        } else {
3140            panic!("expected Program");
3141        }
3142    }
3143
3144    // -----------------------------------------------------------------------
3145    // P2.2 binding-pattern leaves (driven via the test-only wrapper, since they
3146    // are not reachable from a statement until P2.3).
3147    // -----------------------------------------------------------------------
3148
3149    #[test]
3150    fn binding_array_pattern_basic() {
3151        use hermes_ast::context::Context;
3152        use hermes_ast::node::Node;
3153        use hermes_support::manager::SourceErrorManager;
3154
3155        let mut sm = SourceErrorManager::new();
3156        let buf_id = sm.add_buffer_bytes("input", b"[a, , ...b]");
3157        let mut ctx = Context::new();
3158        let gc = ctx.lock();
3159        let atoms = &gc.ctx().atom_table;
3160        let lexer = crate::lexer::JSLexer::new(
3161            buf_id,
3162            &mut sm,
3163            atoms,
3164            crate::lexer::GrammarContext::AllowRegExp,
3165        );
3166        let mut parser = JSParserImpl::new(&gc, lexer);
3167        let pat = parser
3168            .parse_binding_pattern_for_test()
3169            .expect("array binding pattern parses");
3170        assert_eq!(parser.error_count_pub(), 0, "no errors");
3171
3172        let ap = match pat {
3173            Node::ArrayPattern(ap) => ap,
3174            other => panic!("expected ArrayPattern, got {:?}", other.kind()),
3175        };
3176        let elems: Vec<&Node> = ap.elements.iter().collect();
3177        assert_eq!(elems.len(), 3, "three elements");
3178        assert!(
3179            matches!(elems[0], Node::Identifier(_)),
3180            "elem0 = Identifier(a)"
3181        );
3182        assert!(matches!(elems[1], Node::Empty(_)), "elem1 = Empty hole");
3183        match elems[2] {
3184            Node::RestElement(r) => {
3185                assert!(
3186                    matches!(r.argument, Node::Identifier(_)),
3187                    "rest arg = Identifier(b)"
3188                );
3189            }
3190            other => panic!("elem2 should be RestElement, got {:?}", other.kind()),
3191        }
3192    }
3193
3194    #[test]
3195    fn binding_array_pattern_default_initializer() {
3196        use hermes_ast::context::Context;
3197        use hermes_ast::node::Node;
3198        use hermes_support::manager::SourceErrorManager;
3199
3200        let mut sm = SourceErrorManager::new();
3201        let buf_id = sm.add_buffer_bytes("input", b"[a = 1]");
3202        let mut ctx = Context::new();
3203        let gc = ctx.lock();
3204        let atoms = &gc.ctx().atom_table;
3205        let lexer = crate::lexer::JSLexer::new(
3206            buf_id,
3207            &mut sm,
3208            atoms,
3209            crate::lexer::GrammarContext::AllowRegExp,
3210        );
3211        let mut parser = JSParserImpl::new(&gc, lexer);
3212        let pat = parser
3213            .parse_binding_pattern_for_test()
3214            .expect("array binding pattern with default parses");
3215        assert_eq!(parser.error_count_pub(), 0, "no errors");
3216
3217        let ap = match pat {
3218            Node::ArrayPattern(ap) => ap,
3219            other => panic!("expected ArrayPattern, got {:?}", other.kind()),
3220        };
3221        let elems: Vec<&Node> = ap.elements.iter().collect();
3222        assert_eq!(elems.len(), 1, "one element");
3223        match elems[0] {
3224            Node::AssignmentPattern(asn) => {
3225                assert!(
3226                    matches!(asn.left, Node::Identifier(_)),
3227                    "left = Identifier(a)"
3228                );
3229                assert!(
3230                    matches!(asn.right, Node::NumericLiteral(_)),
3231                    "right = NumericLiteral(1)"
3232                );
3233            }
3234            other => {
3235                panic!("elem0 should be AssignmentPattern, got {:?}", other.kind())
3236            }
3237        }
3238    }
3239
3240    #[test]
3241    fn binding_object_pattern_basic() {
3242        use hermes_ast::context::Context;
3243        use hermes_ast::node::Node;
3244        use hermes_support::manager::SourceErrorManager;
3245
3246        let mut sm = SourceErrorManager::new();
3247        let buf_id = sm.add_buffer_bytes("input", b"{a, b: c, d = 1, ...r}");
3248        let mut ctx = Context::new();
3249        let gc = ctx.lock();
3250        let atoms = &gc.ctx().atom_table;
3251        let lexer = crate::lexer::JSLexer::new(
3252            buf_id,
3253            &mut sm,
3254            atoms,
3255            crate::lexer::GrammarContext::AllowRegExp,
3256        );
3257        let mut parser = JSParserImpl::new(&gc, lexer);
3258        let pat = parser
3259            .parse_binding_pattern_for_test()
3260            .expect("object binding pattern parses");
3261        assert_eq!(parser.error_count_pub(), 0, "no errors");
3262
3263        let op = match pat {
3264            Node::ObjectPattern(op) => op,
3265            other => panic!("expected ObjectPattern, got {:?}", other.kind()),
3266        };
3267        let props: Vec<&Node> = op.properties.iter().collect();
3268        assert_eq!(props.len(), 4, "four properties");
3269
3270        // {a} — shorthand Property whose value is a fresh Identifier.
3271        match props[0] {
3272            Node::Property(p) => {
3273                assert!(p.shorthand.get(), "a is shorthand");
3274                assert!(!p.computed.get(), "a not computed");
3275                assert!(matches!(p.key, Node::Identifier(_)), "key = a");
3276                assert!(matches!(p.value, Node::Identifier(_)), "value = a");
3277            }
3278            other => panic!("prop0 should be Property, got {:?}", other.kind()),
3279        }
3280
3281        // {b: c} — keyed Property, value Identifier(c), not shorthand.
3282        match props[1] {
3283            Node::Property(p) => {
3284                assert!(!p.shorthand.get(), "b:c not shorthand");
3285                assert!(matches!(p.key, Node::Identifier(_)), "key = b");
3286                assert!(matches!(p.value, Node::Identifier(_)), "value = c");
3287            }
3288            other => panic!("prop1 should be Property, got {:?}", other.kind()),
3289        }
3290
3291        // {d = 1} — Property whose value is an AssignmentPattern.
3292        match props[2] {
3293            Node::Property(p) => {
3294                assert!(p.shorthand.get(), "d = 1 is shorthand");
3295                match p.value {
3296                    Node::AssignmentPattern(asn) => {
3297                        assert!(
3298                            matches!(asn.left, Node::Identifier(_)),
3299                            "left = d"
3300                        );
3301                        assert!(
3302                            matches!(asn.right, Node::NumericLiteral(_)),
3303                            "right = 1"
3304                        );
3305                    }
3306                    other => panic!(
3307                        "prop2 value should be AssignmentPattern, got {:?}",
3308                        other.kind()
3309                    ),
3310                }
3311            }
3312            other => panic!("prop2 should be Property, got {:?}", other.kind()),
3313        }
3314
3315        // {...r} — RestElement whose argument is an Identifier.
3316        match props[3] {
3317            Node::RestElement(r) => {
3318                assert!(
3319                    matches!(r.argument, Node::Identifier(_)),
3320                    "rest arg = r"
3321                );
3322            }
3323            other => panic!("prop3 should be RestElement, got {:?}", other.kind()),
3324        }
3325    }
3326
3327    // -----------------------------------------------------------------------
3328    // P2.3: variable declarations (var/let/const/using).
3329    // -----------------------------------------------------------------------
3330
3331    /// Parse `src`, returning the parser so the caller can inspect the program
3332    /// and (via a `CollectingHandler`) the emitted diagnostics. The handler is
3333    /// installed before parsing so error message text is captured.
3334    fn parse_with_collector<'gc>(
3335        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
3336        sm: &mut hermes_support::manager::SourceErrorManager,
3337        atoms: &hermes_atom_table::AtomTable,
3338        src: &[u8],
3339    ) -> Option<&'gc hermes_ast::node::Node<'gc>> {
3340        sm.set_handler(Box::new(hermes_support::diag::CollectingHandler::new()));
3341        let buf_id = sm.add_buffer_bytes("input", src);
3342        let lexer = crate::lexer::JSLexer::new(
3343            buf_id,
3344            sm,
3345            atoms,
3346            crate::lexer::GrammarContext::AllowRegExp,
3347        );
3348        let mut parser = JSParserImpl::new(gc, lexer);
3349        parser.parse()
3350    }
3351
3352    /// `var [a] = b;` → a VariableDeclaration with kind "var" whose single
3353    /// declarator's `id` is an ArrayPattern.
3354    #[test]
3355    fn var_array_pattern_declaration() {
3356        use hermes_ast::context::Context;
3357        use hermes_ast::node::Node;
3358        use hermes_support::manager::SourceErrorManager;
3359
3360        let mut sm = SourceErrorManager::new();
3361        let buf_id = sm.add_buffer_bytes("input", b"var [a] = b;");
3362        let mut ctx = Context::new();
3363        let gc = ctx.lock();
3364        let atoms = &gc.ctx().atom_table;
3365        let lexer = crate::lexer::JSLexer::new(
3366            buf_id,
3367            &mut sm,
3368            atoms,
3369            crate::lexer::GrammarContext::AllowRegExp,
3370        );
3371        let mut parser = JSParserImpl::new(&gc, lexer);
3372        let program = parser.parse().expect("var [a] = b; parses");
3373        assert_eq!(parser.error_count_pub(), 0);
3374
3375        let Node::Program(p) = program else {
3376            panic!("expected Program")
3377        };
3378        let stmt = p.body.iter().next().expect("one statement");
3379        let Node::VariableDeclaration(vd) = stmt else {
3380            panic!("expected VariableDeclaration, got {:?}", stmt.kind())
3381        };
3382        assert_eq!(
3383            gc.ctx().atom_table.bytes(vd.kind.get()),
3384            b"var",
3385            "kind should be 'var'"
3386        );
3387        let decl = vd.declarations.iter().next().expect("one declarator");
3388        let Node::VariableDeclarator(d) = decl else {
3389            panic!("expected VariableDeclarator")
3390        };
3391        assert!(
3392            matches!(d.id, Node::ArrayPattern(_)),
3393            "declarator id should be ArrayPattern, got {:?}",
3394            d.id.kind()
3395        );
3396        assert!(d.init.is_some(), "declarator should have an initializer");
3397    }
3398
3399    /// `const x;` → reports "missing initializer in const declaration".
3400    #[test]
3401    fn const_without_initializer_errors() {
3402        use hermes_ast::context::Context;
3403        use hermes_support::diag::{CollectingHandler, DiagKind};
3404        use hermes_support::manager::SourceErrorManager;
3405
3406        let mut sm = SourceErrorManager::new();
3407        let mut ctx = Context::new();
3408        let gc = ctx.lock();
3409        let atoms = &gc.ctx().atom_table;
3410        let _program = parse_with_collector(&gc, &mut sm, atoms, b"const x;");
3411
3412        let h = sm.handler_as::<CollectingHandler>().unwrap();
3413        let errs: Vec<_> = h
3414            .messages()
3415            .iter()
3416            .filter(|m| m.kind == DiagKind::Error)
3417            .collect();
3418        assert!(
3419            errs.iter()
3420                .any(|m| m.message == "missing initializer in const declaration"),
3421            "expected 'missing initializer in const declaration', got {:?}",
3422            errs.iter().map(|m| &m.message).collect::<Vec<_>>()
3423        );
3424    }
3425
3426    /// `var [a];` → reports "destucturing declaration must be initialized"
3427    /// (the C++ typo "destucturing" is preserved).
3428    #[test]
3429    fn destructuring_without_initializer_errors() {
3430        use hermes_ast::context::Context;
3431        use hermes_support::diag::{CollectingHandler, DiagKind};
3432        use hermes_support::manager::SourceErrorManager;
3433
3434        let mut sm = SourceErrorManager::new();
3435        let mut ctx = Context::new();
3436        let gc = ctx.lock();
3437        let atoms = &gc.ctx().atom_table;
3438        let _program = parse_with_collector(&gc, &mut sm, atoms, b"var [a];");
3439
3440        let h = sm.handler_as::<CollectingHandler>().unwrap();
3441        let errs: Vec<_> = h
3442            .messages()
3443            .iter()
3444            .filter(|m| m.kind == DiagKind::Error)
3445            .collect();
3446        assert!(
3447            errs.iter()
3448                .any(|m| m.message == "destucturing declaration must be initialized"),
3449            "expected 'destucturing declaration must be initialized', got {:?}",
3450            errs.iter().map(|m| &m.message).collect::<Vec<_>>()
3451        );
3452    }
3453
3454    /// `let x = 1;` → VariableDeclaration with kind "let".
3455    #[test]
3456    fn let_declaration_kind() {
3457        use hermes_ast::context::Context;
3458        use hermes_ast::node::Node;
3459        use hermes_support::manager::SourceErrorManager;
3460
3461        let mut sm = SourceErrorManager::new();
3462        let buf_id = sm.add_buffer_bytes("input", b"let x = 1;");
3463        let mut ctx = Context::new();
3464        let gc = ctx.lock();
3465        let atoms = &gc.ctx().atom_table;
3466        let lexer = crate::lexer::JSLexer::new(
3467            buf_id,
3468            &mut sm,
3469            atoms,
3470            crate::lexer::GrammarContext::AllowRegExp,
3471        );
3472        let mut parser = JSParserImpl::new(&gc, lexer);
3473        let program = parser.parse().expect("let x = 1; parses");
3474        assert_eq!(parser.error_count_pub(), 0);
3475
3476        let Node::Program(p) = program else {
3477            panic!("expected Program")
3478        };
3479        let stmt = p.body.iter().next().expect("one statement");
3480        let Node::VariableDeclaration(vd) = stmt else {
3481            panic!("expected VariableDeclaration, got {:?}", stmt.kind())
3482        };
3483        assert_eq!(
3484            gc.ctx().atom_table.bytes(vd.kind.get()),
3485            b"let",
3486            "kind should be 'let'"
3487        );
3488    }
3489
3490    /// Sloppy-mode `let;` is a loose identifier expression, not a declaration:
3491    /// it must parse as an ExpressionStatement. (Regression for the P1
3492    /// always-flag-`let` approximation now replaced by the real
3493    /// `is_let_followed_by_decl_start` lookahead.)
3494    #[test]
3495    fn loose_let_is_expression_statement() {
3496        use hermes_ast::context::Context;
3497        use hermes_ast::node::Node;
3498        use hermes_support::manager::SourceErrorManager;
3499
3500        let mut sm = SourceErrorManager::new();
3501        let buf_id = sm.add_buffer_bytes("input", b"let;\nlet x;");
3502        let mut ctx = Context::new();
3503        let gc = ctx.lock();
3504        let atoms = &gc.ctx().atom_table;
3505        let lexer = crate::lexer::JSLexer::new(
3506            buf_id,
3507            &mut sm,
3508            atoms,
3509            crate::lexer::GrammarContext::AllowRegExp,
3510        );
3511        let mut parser = JSParserImpl::new(&gc, lexer);
3512        let program = parser.parse().expect("let;\\nlet x; parses");
3513        assert_eq!(parser.error_count_pub(), 0);
3514
3515        let Node::Program(p) = program else {
3516            panic!("expected Program")
3517        };
3518        let mut it = p.body.iter();
3519        // First: `let;` → ExpressionStatement (loose-mode identifier `let`).
3520        let first = it.next().expect("first statement");
3521        assert!(
3522            matches!(first, Node::ExpressionStatement(_)),
3523            "`let;` should be an ExpressionStatement, got {:?}",
3524            first.kind()
3525        );
3526        // Second: `let x;` → VariableDeclaration.
3527        let second = it.next().expect("second statement");
3528        assert!(
3529            matches!(second, Node::VariableDeclaration(_)),
3530            "`let x;` should be a VariableDeclaration, got {:?}",
3531            second.kind()
3532        );
3533    }
3534
3535    // -----------------------------------------------------------------------
3536    // P2.4: block / if / while / do-while / switch / try statements.
3537    // -----------------------------------------------------------------------
3538
3539    /// Helper: parse `src`, expect success with zero errors, return the first
3540    /// statement of the Program body.
3541    fn parse_first_stmt<'gc>(
3542        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
3543        sm: &mut hermes_support::manager::SourceErrorManager,
3544        src: &[u8],
3545    ) -> &'gc hermes_ast::node::Node<'gc> {
3546        let buf_id = sm.add_buffer_bytes("input", src);
3547        let atoms = &gc.ctx().atom_table;
3548        let lexer = crate::lexer::JSLexer::new(
3549            buf_id,
3550            sm,
3551            atoms,
3552            crate::lexer::GrammarContext::AllowRegExp,
3553        );
3554        let mut parser = JSParserImpl::new(gc, lexer);
3555        let program = parser.parse().expect("parse succeeded");
3556        assert_eq!(parser.error_count_pub(), 0, "zero errors");
3557        if let hermes_ast::node::Node::Program(p) = program {
3558            return p.body.iter().next().expect("has statement");
3559        }
3560        panic!("expected Program");
3561    }
3562
3563    /// `{{x;}}` → a BlockStatement whose single child is a BlockStatement.
3564    #[test]
3565    fn nested_block_statement() {
3566        use hermes_ast::context::Context;
3567        use hermes_ast::node::Node;
3568        let mut sm = hermes_support::manager::SourceErrorManager::new();
3569        let mut ctx = Context::new();
3570        let gc = ctx.lock();
3571        let stmt = parse_first_stmt(&gc, &mut sm, b"{{x;}}");
3572        let Node::BlockStatement(outer) = stmt else {
3573            panic!("expected BlockStatement, got {:?}", stmt.kind())
3574        };
3575        let inner = outer.body.iter().next().expect("one inner statement");
3576        assert!(
3577            matches!(inner, Node::BlockStatement(_)),
3578            "inner statement should be BlockStatement, got {:?}",
3579            inner.kind()
3580        );
3581    }
3582
3583    /// `if(a)b;else c;` → IfStatement with a non-None alternate.
3584    #[test]
3585    fn if_with_else() {
3586        use hermes_ast::context::Context;
3587        use hermes_ast::node::Node;
3588        let mut sm = hermes_support::manager::SourceErrorManager::new();
3589        let mut ctx = Context::new();
3590        let gc = ctx.lock();
3591        let stmt = parse_first_stmt(&gc, &mut sm, b"if(a)b;else c;");
3592        let Node::IfStatement(iff) = stmt else {
3593            panic!("expected IfStatement, got {:?}", stmt.kind())
3594        };
3595        assert!(iff.alternate.is_some(), "alternate should be present");
3596    }
3597
3598    /// `if(a)if(b)c;else d;` → the else binds to the INNER if (dangling-else).
3599    #[test]
3600    fn dangling_else_binds_to_inner_if() {
3601        use hermes_ast::context::Context;
3602        use hermes_ast::node::Node;
3603        let mut sm = hermes_support::manager::SourceErrorManager::new();
3604        let mut ctx = Context::new();
3605        let gc = ctx.lock();
3606        let stmt = parse_first_stmt(&gc, &mut sm, b"if(a)if(b)c;else d;");
3607        let Node::IfStatement(outer) = stmt else {
3608            panic!("expected IfStatement, got {:?}", stmt.kind())
3609        };
3610        // Outer if has no else; its consequent is the inner if which DOES.
3611        assert!(
3612            outer.alternate.is_none(),
3613            "outer if should have no alternate"
3614        );
3615        let Node::IfStatement(inner) = outer.consequent else {
3616            panic!(
3617                "outer consequent should be IfStatement, got {:?}",
3618                outer.consequent.kind()
3619            )
3620        };
3621        assert!(
3622            inner.alternate.is_some(),
3623            "else should bind to the inner if"
3624        );
3625    }
3626
3627    /// `while(x)y;` → WhileStatement whose `test` is the Identifier `x` and
3628    /// whose `body` is the ExpressionStatement `y;` (asserts body/test are NOT
3629    /// swapped — the C++ ctor takes body first).
3630    #[test]
3631    fn while_body_and_test_not_swapped() {
3632        use hermes_ast::context::Context;
3633        use hermes_ast::node::Node;
3634        let mut sm = hermes_support::manager::SourceErrorManager::new();
3635        let mut ctx = Context::new();
3636        let gc = ctx.lock();
3637        let stmt = parse_first_stmt(&gc, &mut sm, b"while(x)y;");
3638        let Node::WhileStatement(w) = stmt else {
3639            panic!("expected WhileStatement, got {:?}", stmt.kind())
3640        };
3641        // test must be the Identifier `x`.
3642        let Node::Identifier(id) = w.test else {
3643            panic!("test should be Identifier(x), got {:?}", w.test.kind())
3644        };
3645        assert_eq!(gc.ctx().atom_table.bytes(id.name.get()), b"x");
3646        // body must be the ExpressionStatement `y;`.
3647        assert!(
3648            matches!(w.body, Node::ExpressionStatement(_)),
3649            "body should be ExpressionStatement, got {:?}",
3650            w.body.kind()
3651        );
3652    }
3653
3654    /// `switch(x){default:;default:;}` → reports "more than one 'default'
3655    /// clause in 'switch'".
3656    #[test]
3657    fn switch_duplicate_default_errors() {
3658        use hermes_ast::context::Context;
3659        use hermes_support::diag::{CollectingHandler, DiagKind};
3660        let mut sm = hermes_support::manager::SourceErrorManager::new();
3661        let mut ctx = Context::new();
3662        let gc = ctx.lock();
3663        let atoms = &gc.ctx().atom_table;
3664        let _program = parse_with_collector(
3665            &gc,
3666            &mut sm,
3667            atoms,
3668            b"switch(x){default:;default:;}",
3669        );
3670        let h = sm.handler_as::<CollectingHandler>().unwrap();
3671        let errs: Vec<_> = h
3672            .messages()
3673            .iter()
3674            .filter(|m| m.kind == DiagKind::Error)
3675            .collect();
3676        assert!(
3677            errs.iter().any(|m| m.message
3678                == "more than one 'default' clause in 'switch'"),
3679            "expected duplicate-default error, got {:?}",
3680            errs.iter().map(|m| &m.message).collect::<Vec<_>>()
3681        );
3682    }
3683
3684    /// `try{}` (no catch/finally) → reports the catch/finally expected error.
3685    #[test]
3686    fn try_without_handler_errors() {
3687        use hermes_ast::context::Context;
3688        use hermes_support::diag::{CollectingHandler, DiagKind};
3689        let mut sm = hermes_support::manager::SourceErrorManager::new();
3690        let mut ctx = Context::new();
3691        let gc = ctx.lock();
3692        let atoms = &gc.ctx().atom_table;
3693        let _program = parse_with_collector(&gc, &mut sm, atoms, b"try{}");
3694        let h = sm.handler_as::<CollectingHandler>().unwrap();
3695        let errs: Vec<_> = h
3696            .messages()
3697            .iter()
3698            .filter(|m| m.kind == DiagKind::Error)
3699            .collect();
3700        assert!(
3701            errs.iter().any(|m| m
3702                .message
3703                .contains("'catch' or 'finally' expected")),
3704            "expected catch/finally error, got {:?}",
3705            errs.iter().map(|m| &m.message).collect::<Vec<_>>()
3706        );
3707    }
3708
3709    /// `for(a in b)c;` → ForInStatement: left is Identifier(a), right is
3710    /// Identifier(b).
3711    #[test]
3712    fn for_in_basic() {
3713        use hermes_ast::context::Context;
3714        use hermes_ast::node::Node;
3715        let mut sm = hermes_support::manager::SourceErrorManager::new();
3716        let mut ctx = Context::new();
3717        let gc = ctx.lock();
3718        let stmt = parse_first_stmt(&gc, &mut sm, b"for(a in b)c;");
3719        let Node::ForInStatement(f) = stmt else {
3720            panic!("expected ForInStatement, got {:?}", stmt.kind())
3721        };
3722        let Node::Identifier(left) = f.left else {
3723            panic!("left should be Identifier(a), got {:?}", f.left.kind())
3724        };
3725        assert_eq!(gc.ctx().atom_table.bytes(left.name.get()), b"a");
3726        let Node::Identifier(right) = f.right else {
3727            panic!("right should be Identifier(b), got {:?}", f.right.kind())
3728        };
3729        assert_eq!(gc.ctx().atom_table.bytes(right.name.get()), b"b");
3730    }
3731
3732    /// `for([a] of b)c;` → ForOfStatement whose `left` is an ArrayPattern
3733    /// (the `[a]` cover expression was reparsed into a pattern).
3734    #[test]
3735    fn for_of_array_pattern_left() {
3736        use hermes_ast::context::Context;
3737        use hermes_ast::node::Node;
3738        let mut sm = hermes_support::manager::SourceErrorManager::new();
3739        let mut ctx = Context::new();
3740        let gc = ctx.lock();
3741        let stmt = parse_first_stmt(&gc, &mut sm, b"for([a] of b)c;");
3742        let Node::ForOfStatement(f) = stmt else {
3743            panic!("expected ForOfStatement, got {:?}", stmt.kind())
3744        };
3745        assert!(
3746            matches!(f.left, Node::ArrayPattern(_)),
3747            "left should be ArrayPattern, got {:?}",
3748            f.left.kind()
3749        );
3750        assert!(!f.r#await.get(), "await should be false");
3751    }
3752
3753    /// `for(var a, b in c);` → reports "Only one binding must be declared in a
3754    /// for-in/for-of loop".
3755    #[test]
3756    fn for_in_multiple_bindings_errors() {
3757        use hermes_ast::context::Context;
3758        use hermes_support::diag::{CollectingHandler, DiagKind};
3759        let mut sm = hermes_support::manager::SourceErrorManager::new();
3760        let mut ctx = Context::new();
3761        let gc = ctx.lock();
3762        let atoms = &gc.ctx().atom_table;
3763        let _program =
3764            parse_with_collector(&gc, &mut sm, atoms, b"for(var a, b in c);");
3765        let h = sm.handler_as::<CollectingHandler>().unwrap();
3766        let errs: Vec<_> = h
3767            .messages()
3768            .iter()
3769            .filter(|m| m.kind == DiagKind::Error)
3770            .collect();
3771        assert!(
3772            errs.iter().any(|m| m.message
3773                == "Only one binding must be declared in a for-in/for-of loop"),
3774            "expected single-binding error, got {:?}",
3775            errs.iter().map(|m| &m.message).collect::<Vec<_>>()
3776        );
3777    }
3778
3779    /// `for(;;);` → ForStatement with init/test/update all None.
3780    #[test]
3781    fn for_empty_head() {
3782        use hermes_ast::context::Context;
3783        use hermes_ast::node::Node;
3784        let mut sm = hermes_support::manager::SourceErrorManager::new();
3785        let mut ctx = Context::new();
3786        let gc = ctx.lock();
3787        let stmt = parse_first_stmt(&gc, &mut sm, b"for(;;);");
3788        let Node::ForStatement(f) = stmt else {
3789            panic!("expected ForStatement, got {:?}", stmt.kind())
3790        };
3791        assert!(f.init.is_none(), "init should be None");
3792        assert!(f.test.is_none(), "test should be None");
3793        assert!(f.update.is_none(), "update should be None");
3794        assert!(
3795            matches!(f.body, Node::EmptyStatement(_)),
3796            "body should be EmptyStatement, got {:?}",
3797            f.body.kind()
3798        );
3799    }
3800
3801    /// `for(var i=0;i<2;i++);` → ForStatement whose `init` is a
3802    /// VariableDeclaration.
3803    #[test]
3804    fn for_c_style_var_init() {
3805        use hermes_ast::context::Context;
3806        use hermes_ast::node::Node;
3807        let mut sm = hermes_support::manager::SourceErrorManager::new();
3808        let mut ctx = Context::new();
3809        let gc = ctx.lock();
3810        let stmt = parse_first_stmt(&gc, &mut sm, b"for(var i=0;i<2;i++);");
3811        let Node::ForStatement(f) = stmt else {
3812            panic!("expected ForStatement, got {:?}", stmt.kind())
3813        };
3814        let init = f.init.expect("init should be Some");
3815        assert!(
3816            matches!(init, Node::VariableDeclaration(_)),
3817            "init should be VariableDeclaration, got {:?}",
3818            init.kind()
3819        );
3820        assert!(f.test.is_some(), "test should be Some");
3821        assert!(f.update.is_some(), "update should be Some");
3822    }
3823
3824    // ------------------------------------------------------------------
3825    // P3.2 — yield expressions
3826    // ------------------------------------------------------------------
3827
3828    /// Parse `src` (a `function* g(){ <body> }`) and return the
3829    /// `YieldExpression` reached as the first statement's expression.
3830    fn first_yield<'gc>(
3831        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
3832        sm: &mut hermes_support::manager::SourceErrorManager,
3833        src: &[u8],
3834    ) -> &'gc hermes_ast::node::YieldExpression<'gc> {
3835        use hermes_ast::node::Node;
3836        let decl = parse_first_stmt(gc, sm, src);
3837        let Node::FunctionDeclaration(f) = decl else {
3838            panic!("expected FunctionDeclaration, got {:?}", decl.kind())
3839        };
3840        let Node::BlockStatement(block) = f.body else {
3841            panic!("expected BlockStatement body, got {:?}", f.body.kind())
3842        };
3843        let first = block.body.iter().next().expect("body has a statement");
3844        let Node::ExpressionStatement(es) = first else {
3845            panic!("expected ExpressionStatement, got {:?}", first.kind())
3846        };
3847        let Node::YieldExpression(y) = es.expression else {
3848            panic!(
3849                "expected YieldExpression, got {:?}",
3850                es.expression.kind()
3851            )
3852        };
3853        y
3854    }
3855
3856    /// `function* g(){ yield* a; }` → delegate=true, argument Some.
3857    #[test]
3858    fn yield_delegate() {
3859        use hermes_ast::context::Context;
3860        let mut sm = hermes_support::manager::SourceErrorManager::new();
3861        let mut ctx = Context::new();
3862        let gc = ctx.lock();
3863        let y = first_yield(&gc, &mut sm, b"function* g(){ yield* a; }");
3864        assert!(y.delegate.get(), "yield* should set delegate=true");
3865        assert!(y.argument.is_some(), "yield* a has an argument");
3866    }
3867
3868    /// `function* g(){ yield; }` → argument None, delegate=false.
3869    #[test]
3870    fn yield_no_argument() {
3871        use hermes_ast::context::Context;
3872        let mut sm = hermes_support::manager::SourceErrorManager::new();
3873        let mut ctx = Context::new();
3874        let gc = ctx.lock();
3875        let y = first_yield(&gc, &mut sm, b"function* g(){ yield; }");
3876        assert!(y.argument.is_none(), "bare yield has no argument");
3877        assert!(!y.delegate.get(), "bare yield is not delegating");
3878    }
3879
3880    /// `function* g(){ yield 1; }` → argument Some, delegate=false.
3881    #[test]
3882    fn yield_with_argument() {
3883        use hermes_ast::context::Context;
3884        let mut sm = hermes_support::manager::SourceErrorManager::new();
3885        let mut ctx = Context::new();
3886        let gc = ctx.lock();
3887        let y = first_yield(&gc, &mut sm, b"function* g(){ yield 1; }");
3888        assert!(y.argument.is_some(), "yield 1 has an argument");
3889        assert!(!y.delegate.get(), "yield 1 is not delegating");
3890    }
3891
3892    // ------------------------------------------------------------------
3893    // P3.3 — arrow functions + cover-paren reparse
3894    // ------------------------------------------------------------------
3895
3896    /// Parse `src` and return the `ArrowFunctionExpression` reached as the
3897    /// first statement's expression.
3898    fn first_arrow<'gc>(
3899        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
3900        sm: &mut hermes_support::manager::SourceErrorManager,
3901        src: &[u8],
3902    ) -> &'gc hermes_ast::node::ArrowFunctionExpression<'gc> {
3903        use hermes_ast::node::Node;
3904        let stmt = parse_first_stmt(gc, sm, src);
3905        let Node::ExpressionStatement(es) = stmt else {
3906            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
3907        };
3908        let Node::ArrowFunctionExpression(a) = es.expression else {
3909            panic!(
3910                "expected ArrowFunctionExpression, got {:?}",
3911                es.expression.kind()
3912            )
3913        };
3914        a
3915    }
3916
3917    /// `a => a;` → expression=true, async=false, params=[Identifier].
3918    #[test]
3919    fn arrow_single_ident() {
3920        use hermes_ast::context::Context;
3921        use hermes_ast::node::Node;
3922        let mut sm = hermes_support::manager::SourceErrorManager::new();
3923        let mut ctx = Context::new();
3924        let gc = ctx.lock();
3925        let a = first_arrow(&gc, &mut sm, b"a => a;");
3926        assert!(a.expression.get(), "concise body is an expression");
3927        assert!(!a.r#async.get(), "not async");
3928        let params: Vec<_> = a.params.iter().collect();
3929        assert_eq!(params.len(), 1, "one param");
3930        assert!(
3931            matches!(params[0], Node::Identifier(_)),
3932            "param is Identifier, got {:?}",
3933            params[0].kind()
3934        );
3935    }
3936
3937    /// `() => 0;` → params empty.
3938    #[test]
3939    fn arrow_empty_params() {
3940        use hermes_ast::context::Context;
3941        let mut sm = hermes_support::manager::SourceErrorManager::new();
3942        let mut ctx = Context::new();
3943        let gc = ctx.lock();
3944        let a = first_arrow(&gc, &mut sm, b"() => 0;");
3945        assert_eq!(a.params.iter().count(), 0, "no params");
3946        assert!(a.expression.get(), "concise body");
3947    }
3948
3949    /// `(a, ...b) => b;` → params=[Identifier, RestElement].
3950    #[test]
3951    fn arrow_rest_param() {
3952        use hermes_ast::context::Context;
3953        use hermes_ast::node::Node;
3954        let mut sm = hermes_support::manager::SourceErrorManager::new();
3955        let mut ctx = Context::new();
3956        let gc = ctx.lock();
3957        let a = first_arrow(&gc, &mut sm, b"(a, ...b) => b;");
3958        let params: Vec<_> = a.params.iter().collect();
3959        assert_eq!(params.len(), 2, "two params");
3960        assert!(
3961            matches!(params[0], Node::Identifier(_)),
3962            "first param Identifier, got {:?}",
3963            params[0].kind()
3964        );
3965        assert!(
3966            matches!(params[1], Node::RestElement(_)),
3967            "second param RestElement, got {:?}",
3968            params[1].kind()
3969        );
3970    }
3971
3972    /// `(a = 1) => a;` → params=[AssignmentPattern].
3973    #[test]
3974    fn arrow_default_param() {
3975        use hermes_ast::context::Context;
3976        use hermes_ast::node::Node;
3977        let mut sm = hermes_support::manager::SourceErrorManager::new();
3978        let mut ctx = Context::new();
3979        let gc = ctx.lock();
3980        let a = first_arrow(&gc, &mut sm, b"(a = 1) => a;");
3981        let params: Vec<_> = a.params.iter().collect();
3982        assert_eq!(params.len(), 1, "one param");
3983        assert!(
3984            matches!(params[0], Node::AssignmentPattern(_)),
3985            "param AssignmentPattern, got {:?}",
3986            params[0].kind()
3987        );
3988    }
3989
3990    /// `({x}) => x;` → params=[ObjectPattern].
3991    #[test]
3992    fn arrow_object_pattern_param() {
3993        use hermes_ast::context::Context;
3994        use hermes_ast::node::Node;
3995        let mut sm = hermes_support::manager::SourceErrorManager::new();
3996        let mut ctx = Context::new();
3997        let gc = ctx.lock();
3998        let a = first_arrow(&gc, &mut sm, b"({x}) => x;");
3999        let params: Vec<_> = a.params.iter().collect();
4000        assert_eq!(params.len(), 1, "one param");
4001        assert!(
4002            matches!(params[0], Node::ObjectPattern(_)),
4003            "param ObjectPattern, got {:?}",
4004            params[0].kind()
4005        );
4006    }
4007
4008    /// `a => { return a; };` → expression=false (block body).
4009    #[test]
4010    fn arrow_block_body() {
4011        use hermes_ast::context::Context;
4012        use hermes_ast::node::Node;
4013        let mut sm = hermes_support::manager::SourceErrorManager::new();
4014        let mut ctx = Context::new();
4015        let gc = ctx.lock();
4016        let a = first_arrow(&gc, &mut sm, b"a => { return a; };");
4017        assert!(!a.expression.get(), "block body is not an expression");
4018        assert!(
4019            matches!(a.body, Node::BlockStatement(_)),
4020            "block body, got {:?}",
4021            a.body.kind()
4022        );
4023    }
4024
4025    /// `async a => a;` → async=true.
4026    #[test]
4027    fn arrow_async_single_ident() {
4028        use hermes_ast::context::Context;
4029        let mut sm = hermes_support::manager::SourceErrorManager::new();
4030        let mut ctx = Context::new();
4031        let gc = ctx.lock();
4032        let a = first_arrow(&gc, &mut sm, b"async a => a;");
4033        assert!(a.r#async.get(), "async arrow");
4034        assert_eq!(a.params.iter().count(), 1, "one param");
4035    }
4036
4037    /// `async (a) => a;` → async=true, params=[Identifier] (parsed via the
4038    /// async-CallExpression cover head).
4039    #[test]
4040    fn arrow_async_paren() {
4041        use hermes_ast::context::Context;
4042        use hermes_ast::node::Node;
4043        let mut sm = hermes_support::manager::SourceErrorManager::new();
4044        let mut ctx = Context::new();
4045        let gc = ctx.lock();
4046        let a = first_arrow(&gc, &mut sm, b"async (a) => a;");
4047        assert!(a.r#async.get(), "async arrow");
4048        let params: Vec<_> = a.params.iter().collect();
4049        assert_eq!(params.len(), 1, "one param");
4050        assert!(
4051            matches!(params[0], Node::Identifier(_)),
4052            "param Identifier, got {:?}",
4053            params[0].kind()
4054        );
4055    }
4056
4057    /// Non-arrow `(a)` → a parenthesized Identifier (parens recorded).
4058    #[test]
4059    fn paren_ident_not_arrow() {
4060        use hermes_ast::context::Context;
4061        use hermes_ast::node::Node;
4062        let mut sm = hermes_support::manager::SourceErrorManager::new();
4063        let mut ctx = Context::new();
4064        let gc = ctx.lock();
4065        let stmt = parse_first_stmt(&gc, &mut sm, b"(a);");
4066        let Node::ExpressionStatement(es) = stmt else {
4067            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
4068        };
4069        assert!(
4070            matches!(es.expression, Node::Identifier(_)),
4071            "expression is Identifier, got {:?}",
4072            es.expression.kind()
4073        );
4074        assert_eq!(
4075            es.expression.metadata().parens.get(),
4076            1,
4077            "one paren recorded"
4078        );
4079    }
4080
4081    /// Non-arrow `(a, b)` → SequenceExpression.
4082    #[test]
4083    fn paren_sequence_not_arrow() {
4084        use hermes_ast::context::Context;
4085        use hermes_ast::node::Node;
4086        let mut sm = hermes_support::manager::SourceErrorManager::new();
4087        let mut ctx = Context::new();
4088        let gc = ctx.lock();
4089        let stmt = parse_first_stmt(&gc, &mut sm, b"(a, b);");
4090        let Node::ExpressionStatement(es) = stmt else {
4091            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
4092        };
4093        assert!(
4094            matches!(es.expression, Node::SequenceExpression(_)),
4095            "expression is SequenceExpression, got {:?}",
4096            es.expression.kind()
4097        );
4098    }
4099
4100    /// Non-arrow `(a,)` → SequenceExpression whose last element is a
4101    /// `CoverTrailingComma` (matches hermesc — the cover node survives into the
4102    /// AST when not followed by `=>`).
4103    #[test]
4104    fn paren_trailing_comma_not_arrow() {
4105        use hermes_ast::context::Context;
4106        use hermes_ast::node::Node;
4107        let mut sm = hermes_support::manager::SourceErrorManager::new();
4108        let mut ctx = Context::new();
4109        let gc = ctx.lock();
4110        let stmt = parse_first_stmt(&gc, &mut sm, b"(a,);");
4111        let Node::ExpressionStatement(es) = stmt else {
4112            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
4113        };
4114        let Node::SequenceExpression(seq) = es.expression else {
4115            panic!(
4116                "expected SequenceExpression, got {:?}",
4117                es.expression.kind()
4118            )
4119        };
4120        let elems: Vec<_> = seq.expressions.iter().collect();
4121        assert_eq!(elems.len(), 2, "[a, CoverTrailingComma]");
4122        assert!(
4123            matches!(elems[1], Node::CoverTrailingComma(_)),
4124            "last element CoverTrailingComma, got {:?}",
4125            elems[1].kind()
4126        );
4127    }
4128
4129    /// Drill into `({ m() { return <expr>; } });` and return the return
4130    /// statement's argument, for the `super` tests below.
4131    #[cfg(test)]
4132    fn return_arg_in_object_method<'gc>(
4133        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4134        sm: &mut hermes_support::manager::SourceErrorManager,
4135        src: &[u8],
4136    ) -> &'gc hermes_ast::node::Node<'gc> {
4137        use hermes_ast::node::Node;
4138        let stmt = parse_first_stmt(gc, sm, src);
4139        let Node::ExpressionStatement(es) = stmt else {
4140            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
4141        };
4142        let Node::ObjectExpression(obj) = es.expression else {
4143            panic!("expected ObjectExpression, got {:?}", es.expression.kind())
4144        };
4145        let prop = obj.properties.iter().next().expect("has property");
4146        let Node::Property(prop) = prop else {
4147            panic!("expected Property, got {:?}", prop.kind())
4148        };
4149        let Node::FunctionExpression(func) = prop.value else {
4150            panic!("expected FunctionExpression, got {:?}", prop.value.kind())
4151        };
4152        let Node::BlockStatement(block) = func.body else {
4153            panic!("expected BlockStatement, got {:?}", func.body.kind())
4154        };
4155        let ret = block.body.iter().next().expect("has return statement");
4156        let Node::ReturnStatement(ret) = ret else {
4157            panic!("expected ReturnStatement, got {:?}", ret.kind())
4158        };
4159        ret.argument.expect("return has argument")
4160    }
4161
4162    /// `super.x` (in an object method) → a non-computed MemberExpression whose
4163    /// object is a `Super` node.
4164    #[test]
4165    fn super_member_dot() {
4166        use hermes_ast::context::Context;
4167        use hermes_ast::node::Node;
4168        let mut sm = hermes_support::manager::SourceErrorManager::new();
4169        let mut ctx = Context::new();
4170        let gc = ctx.lock();
4171        let arg = return_arg_in_object_method(
4172            &gc,
4173            &mut sm,
4174            b"({ m() { return super.x; } });",
4175        );
4176        let Node::MemberExpression(member) = arg else {
4177            panic!("expected MemberExpression, got {:?}", arg.kind())
4178        };
4179        assert!(
4180            matches!(member.object, Node::Super(_)),
4181            "object is Super, got {:?}",
4182            member.object.kind()
4183        );
4184        assert!(!member.computed.get(), "super.x is not computed");
4185    }
4186
4187    /// `super['y']` (in an object method) → a computed MemberExpression whose
4188    /// object is a `Super` node.
4189    #[test]
4190    fn super_member_computed() {
4191        use hermes_ast::context::Context;
4192        use hermes_ast::node::Node;
4193        let mut sm = hermes_support::manager::SourceErrorManager::new();
4194        let mut ctx = Context::new();
4195        let gc = ctx.lock();
4196        let arg = return_arg_in_object_method(
4197            &gc,
4198            &mut sm,
4199            b"({ m() { return super['y']; } });",
4200        );
4201        let Node::MemberExpression(member) = arg else {
4202            panic!("expected MemberExpression, got {:?}", arg.kind())
4203        };
4204        assert!(
4205            matches!(member.object, Node::Super(_)),
4206            "object is Super, got {:?}",
4207            member.object.kind()
4208        );
4209        assert!(member.computed.get(), "super['y'] is computed");
4210    }
4211
4212    // P5.0: Flow type alias parsing (js/flow/).
4213
4214    /// Helper: parse `src` with Flow parsing enabled and assert at least one
4215    /// error was reported (the honest-deferral checks for unported Flow
4216    /// productions).
4217    fn assert_flow_parse_has_errors(src: &[u8], why: &str) {
4218        assert_parse_has_errors_impl(src, why, true);
4219    }
4220
4221    /// `type X = number;` → TypeAlias{id "X", no type params, right
4222    /// NumberTypeAnnotation}.
4223    #[test]
4224    fn flow_type_alias_number() {
4225        use hermes_ast::context::Context;
4226        use hermes_ast::node::Node;
4227        let mut sm = hermes_support::manager::SourceErrorManager::new();
4228        let mut ctx = Context::new();
4229        ctx.set_parse_flow(true);
4230        let gc = ctx.lock();
4231        let stmt = parse_one_stmt(&gc, &mut sm, b"type X = number;");
4232        let Node::TypeAlias(alias) = stmt else {
4233            panic!("expected TypeAlias, got {:?}", stmt.kind())
4234        };
4235        assert_eq!(ident_bytes(&gc, alias.id), b"X");
4236        assert!(alias.type_parameters.is_none(), "no type parameters");
4237        assert!(
4238            matches!(alias.right, Node::NumberTypeAnnotation(_)),
4239            "right is NumberTypeAnnotation, got {:?}",
4240            alias.right.kind()
4241        );
4242    }
4243
4244    /// `type X = 'hi';` → TypeAlias whose right is a
4245    /// StringLiteralTypeAnnotation with value "hi".
4246    #[test]
4247    fn flow_type_alias_string_literal() {
4248        use hermes_ast::context::Context;
4249        use hermes_ast::node::Node;
4250        let mut sm = hermes_support::manager::SourceErrorManager::new();
4251        let mut ctx = Context::new();
4252        ctx.set_parse_flow(true);
4253        let gc = ctx.lock();
4254        let stmt = parse_one_stmt(&gc, &mut sm, b"type X = 'hi';");
4255        let Node::TypeAlias(alias) = stmt else {
4256            panic!("expected TypeAlias, got {:?}", stmt.kind())
4257        };
4258        let Node::StringLiteralTypeAnnotation(lit) = alias.right else {
4259            panic!(
4260                "expected StringLiteralTypeAnnotation, got {:?}",
4261                alias.right.kind()
4262            )
4263        };
4264        assert_eq!(gc.ctx().atom_table.bytes(lit.value.get()), b"hi");
4265        assert_eq!(gc.ctx().atom_table.bytes(lit.raw.get()), b"'hi'");
4266    }
4267
4268    /// Without `parse_flow`, `type X = number;` is plain JS: `type` is an
4269    /// ordinary identifier expression and the following `X` is a syntax
4270    /// error, exactly like hermesc without `-parse-flow` ("';' expected").
4271    #[test]
4272    fn flow_disabled_type_alias_is_plain_js() {
4273        assert_parse_has_errors(
4274            b"type X = number;",
4275            "'type X' must not parse as a declaration without parse_flow",
4276        );
4277    }
4278
4279    /// Without `parse_flow`, `type` stays usable as a plain identifier.
4280    #[test]
4281    fn flow_disabled_type_is_plain_identifier() {
4282        use hermes_ast::context::Context;
4283        use hermes_ast::node::Node;
4284        let mut sm = hermes_support::manager::SourceErrorManager::new();
4285        let mut ctx = Context::new();
4286        let gc = ctx.lock();
4287        let stmt = parse_one_stmt(&gc, &mut sm, b"var type = 1;");
4288        assert!(
4289            matches!(stmt, Node::VariableDeclaration(_)),
4290            "expected VariableDeclaration, got {:?}",
4291            stmt.kind()
4292        );
4293    }
4294
4295    // ----------------------------------------------------------------------
4296    // P6.2: Flow `enum` declarations.
4297    // ----------------------------------------------------------------------
4298
4299    /// Helper: lock a Flow context, parse `src`, and return the first
4300    /// statement, which must be an `EnumDeclaration`. The enum body is
4301    /// returned alongside.
4302    fn flow_enum<'gc>(
4303        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4304        sm: &mut hermes_support::manager::SourceErrorManager,
4305        src: &[u8],
4306    ) -> (&'gc hermes_ast::node::Node<'gc>, &'gc hermes_ast::node::Node<'gc>) {
4307        let stmt = parse_one_stmt(gc, sm, src);
4308        let hermes_ast::node::Node::EnumDeclaration(decl) = stmt else {
4309            panic!("expected EnumDeclaration, got {:?}", stmt.kind())
4310        };
4311        (stmt, decl.body)
4312    }
4313
4314    /// `enum E {}` now PARSES (P6.2): it yields an `EnumDeclaration` whose body
4315    /// is an empty, non-explicit `EnumStringBody` (the untyped/empty default).
4316    #[test]
4317    fn flow_enum_empty() {
4318        use hermes_ast::context::Context;
4319        use hermes_ast::node::Node;
4320        let mut sm = hermes_support::manager::SourceErrorManager::new();
4321        let mut ctx = Context::new();
4322        ctx.set_parse_flow(true);
4323        let gc = ctx.lock();
4324        let (_, body) = flow_enum(&gc, &mut sm, b"enum E {}");
4325        let Node::EnumStringBody(b) = body else {
4326            panic!("expected EnumStringBody, got {:?}", body.kind())
4327        };
4328        assert!(b.members.is_empty(), "no members");
4329        assert!(!b.explicit_type.get(), "no explicit type");
4330        assert!(!b.has_unknown_members.get(), "no unknown members");
4331        assert_eq!(sm.error_count(), 0, "no errors");
4332    }
4333
4334    /// `enum E { A, B, C }` → defaulted string-body members.
4335    #[test]
4336    fn flow_enum_defaulted() {
4337        use hermes_ast::context::Context;
4338        use hermes_ast::node::Node;
4339        let mut sm = hermes_support::manager::SourceErrorManager::new();
4340        let mut ctx = Context::new();
4341        ctx.set_parse_flow(true);
4342        let gc = ctx.lock();
4343        let (_, body) = flow_enum(&gc, &mut sm, b"enum E { A, B, C }");
4344        let Node::EnumStringBody(b) = body else {
4345            panic!("expected EnumStringBody, got {:?}", body.kind())
4346        };
4347        assert_eq!(b.members.iter().count(), 3, "three members");
4348        for m in b.members.iter() {
4349            assert!(
4350                matches!(m, Node::EnumDefaultedMember(_)),
4351                "member is defaulted, got {:?}",
4352                m.kind()
4353            );
4354        }
4355        assert_eq!(sm.error_count(), 0);
4356    }
4357
4358    /// `enum N of number { A = 1, B = 2 }` → `EnumNumberBody` with explicit
4359    /// type and `EnumNumberMember`s.
4360    #[test]
4361    fn flow_enum_number_typed() {
4362        use hermes_ast::context::Context;
4363        use hermes_ast::node::Node;
4364        let mut sm = hermes_support::manager::SourceErrorManager::new();
4365        let mut ctx = Context::new();
4366        ctx.set_parse_flow(true);
4367        let gc = ctx.lock();
4368        let (_, body) =
4369            flow_enum(&gc, &mut sm, b"enum N of number { A = 1, B = 2 }");
4370        let Node::EnumNumberBody(b) = body else {
4371            panic!("expected EnumNumberBody, got {:?}", body.kind())
4372        };
4373        assert!(b.explicit_type.get(), "explicit type");
4374        assert_eq!(b.members.iter().count(), 2);
4375        for m in b.members.iter() {
4376            assert!(
4377                matches!(m, Node::EnumNumberMember(_)),
4378                "member is EnumNumberMember, got {:?}",
4379                m.kind()
4380            );
4381        }
4382        assert_eq!(sm.error_count(), 0);
4383    }
4384
4385    /// `enum B of boolean { A = true, B = false }` → `EnumBooleanBody`.
4386    #[test]
4387    fn flow_enum_boolean_typed() {
4388        use hermes_ast::context::Context;
4389        use hermes_ast::node::Node;
4390        let mut sm = hermes_support::manager::SourceErrorManager::new();
4391        let mut ctx = Context::new();
4392        ctx.set_parse_flow(true);
4393        let gc = ctx.lock();
4394        let (_, body) = flow_enum(
4395            &gc,
4396            &mut sm,
4397            b"enum B of boolean { A = true, B = false }",
4398        );
4399        let Node::EnumBooleanBody(b) = body else {
4400            panic!("expected EnumBooleanBody, got {:?}", body.kind())
4401        };
4402        assert!(b.explicit_type.get());
4403        assert!(matches!(
4404            b.members.iter().next().unwrap(),
4405            Node::EnumBooleanMember(_)
4406        ));
4407        assert_eq!(sm.error_count(), 0);
4408    }
4409
4410    /// `enum Y of symbol { A, B }` → `EnumSymbolBody` (which has NO
4411    /// `explicit_type` field) with defaulted members.
4412    #[test]
4413    fn flow_enum_symbol_body_has_no_explicit_type() {
4414        use hermes_ast::context::Context;
4415        use hermes_ast::node::Node;
4416        let mut sm = hermes_support::manager::SourceErrorManager::new();
4417        let mut ctx = Context::new();
4418        ctx.set_parse_flow(true);
4419        let gc = ctx.lock();
4420        let (_, body) = flow_enum(&gc, &mut sm, b"enum Y of symbol { A, B }");
4421        let Node::EnumSymbolBody(b) = body else {
4422            panic!("expected EnumSymbolBody, got {:?}", body.kind())
4423        };
4424        // EnumSymbolBody intentionally has no `explicit_type` field — only
4425        // `members` and `has_unknown_members`. Defaulted symbol members are
4426        // legal.
4427        assert_eq!(b.members.iter().count(), 2);
4428        assert!(!b.has_unknown_members.get());
4429        assert_eq!(sm.error_count(), 0);
4430    }
4431
4432    /// `enum E { A = 1, B = 2, ... }` → inexact body (`has_unknown_members`).
4433    #[test]
4434    fn flow_enum_inexact() {
4435        use hermes_ast::context::Context;
4436        use hermes_ast::node::Node;
4437        let mut sm = hermes_support::manager::SourceErrorManager::new();
4438        let mut ctx = Context::new();
4439        ctx.set_parse_flow(true);
4440        let gc = ctx.lock();
4441        let (_, body) =
4442            flow_enum(&gc, &mut sm, b"enum E { A = 1, B = 2, ... }");
4443        let Node::EnumNumberBody(b) = body else {
4444            panic!("expected EnumNumberBody, got {:?}", body.kind())
4445        };
4446        assert!(b.has_unknown_members.get(), "has unknown members");
4447        assert_eq!(b.members.iter().count(), 2, "two real members");
4448        assert_eq!(sm.error_count(), 0);
4449    }
4450
4451    /// `enum E { A = -1, B = 2 }` → a negated `EnumNumberMember`.
4452    #[test]
4453    fn flow_enum_negative_member() {
4454        use hermes_ast::context::Context;
4455        use hermes_ast::node::Node;
4456        let mut sm = hermes_support::manager::SourceErrorManager::new();
4457        let mut ctx = Context::new();
4458        ctx.set_parse_flow(true);
4459        let gc = ctx.lock();
4460        let (_, body) = flow_enum(&gc, &mut sm, b"enum E { A = -1, B = 2 }");
4461        let Node::EnumNumberBody(b) = body else {
4462            panic!("expected EnumNumberBody, got {:?}", body.kind())
4463        };
4464        let first = b.members.iter().next().unwrap();
4465        let Node::EnumNumberMember(m) = first else {
4466            panic!("expected EnumNumberMember, got {:?}", first.kind())
4467        };
4468        let Node::NumericLiteral(lit) = m.init else {
4469            panic!("expected NumericLiteral, got {:?}", m.init.kind())
4470        };
4471        assert_eq!(lit.value.get(), -1.0, "negated literal");
4472        assert_eq!(sm.error_count(), 0);
4473    }
4474
4475    /// A kind-mismatch (`number` enum with a string member) is a hard error.
4476    #[test]
4477    fn flow_enum_kind_mismatch_errors() {
4478        assert_flow_parse_has_errors(
4479            b"enum N of number { A = 1, B = \"x\" }",
4480            "string initializer in number enum must error",
4481        );
4482    }
4483
4484    /// Inconsistent initializers (some defaulted, some not) is a hard error.
4485    #[test]
4486    fn flow_enum_inconsistent_initializers_errors() {
4487        assert_flow_parse_has_errors(
4488            b"enum E { A = 1, B }",
4489            "mixed initialized/defaulted members must error",
4490        );
4491    }
4492
4493    /// A defaulted-only `number` enum (no inferable values) is a hard error.
4494    #[test]
4495    fn flow_enum_defaulted_number_errors() {
4496        assert_flow_parse_has_errors(
4497            b"enum N of number { A, B }",
4498            "number enums must use initializers",
4499        );
4500    }
4501
4502    /// Without `parse_flow`, `enum` stays a plain identifier.
4503    #[test]
4504    fn flow_disabled_enum_is_plain_identifier() {
4505        use hermes_ast::context::Context;
4506        use hermes_ast::node::Node;
4507        let mut sm = hermes_support::manager::SourceErrorManager::new();
4508        let mut ctx = Context::new();
4509        let gc = ctx.lock();
4510        let stmt = parse_one_stmt(&gc, &mut sm, b"var enum2 = 1;");
4511        assert!(
4512            matches!(stmt, Node::VariableDeclaration(_)),
4513            "expected VariableDeclaration, got {:?}",
4514            stmt.kind()
4515        );
4516    }
4517
4518    // ----------------------------------------------------------------------
4519    // P6.0: Flow ambiguous-expression grammar — `as`/`as const` + type-args.
4520    // ----------------------------------------------------------------------
4521
4522    /// Helper: lock a Flow-ambiguous context (both `parse_flow` and
4523    /// `parse_flow_ambiguous`), parse `src`, and return the first statement's
4524    /// expression (it must be an `ExpressionStatement`).
4525    fn flow_ambiguous_expr<'gc>(
4526        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4527        sm: &mut hermes_support::manager::SourceErrorManager,
4528        src: &[u8],
4529    ) -> &'gc hermes_ast::node::Node<'gc> {
4530        let stmt = parse_one_stmt(gc, sm, src);
4531        let hermes_ast::node::Node::ExpressionStatement(es) = stmt else {
4532            panic!("expected ExpressionStatement, got {:?}", stmt.kind())
4533        };
4534        es.expression
4535    }
4536
4537    /// `x as number` → `AsExpression{ Identifier "x", NumberTypeAnnotation }`.
4538    #[test]
4539    fn flow_as_expression() {
4540        use hermes_ast::context::Context;
4541        use hermes_ast::node::Node;
4542        let mut sm = hermes_support::manager::SourceErrorManager::new();
4543        let mut ctx = Context::new();
4544        ctx.set_parse_flow(true);
4545        ctx.set_parse_flow_ambiguous(true);
4546        let gc = ctx.lock();
4547        let expr = flow_ambiguous_expr(&gc, &mut sm, b"x as number;");
4548        let Node::AsExpression(as_expr) = expr else {
4549            panic!("expected AsExpression, got {:?}", expr.kind())
4550        };
4551        assert_eq!(ident_bytes(&gc, as_expr.expression), b"x");
4552        assert!(
4553            matches!(as_expr.type_annotation, Node::NumberTypeAnnotation(_)),
4554            "type is NumberTypeAnnotation, got {:?}",
4555            as_expr.type_annotation.kind()
4556        );
4557    }
4558
4559    /// `y as const` → `AsConstExpression{ Identifier "y" }` (the `const`
4560    /// special-case, NOT an `AsExpression` over a `GenericTypeAnnotation`).
4561    #[test]
4562    fn flow_as_const_expression() {
4563        use hermes_ast::context::Context;
4564        use hermes_ast::node::Node;
4565        let mut sm = hermes_support::manager::SourceErrorManager::new();
4566        let mut ctx = Context::new();
4567        ctx.set_parse_flow(true);
4568        ctx.set_parse_flow_ambiguous(true);
4569        let gc = ctx.lock();
4570        let expr = flow_ambiguous_expr(&gc, &mut sm, b"y as const;");
4571        let Node::AsConstExpression(as_const) = expr else {
4572            panic!("expected AsConstExpression, got {:?}", expr.kind())
4573        };
4574        assert_eq!(ident_bytes(&gc, as_const.expression), b"y");
4575    }
4576
4577    /// `f<T>()` is a `CallExpression` whose `type_arguments` is populated, and
4578    /// `a < b` rolls the speculation back into a `BinaryExpression`.
4579    #[test]
4580    fn flow_call_type_args_vs_comparison() {
4581        use hermes_ast::context::Context;
4582        use hermes_ast::node::Node;
4583        // f<T>() — type-args kept.
4584        {
4585            let mut sm = hermes_support::manager::SourceErrorManager::new();
4586            let mut ctx = Context::new();
4587            ctx.set_parse_flow(true);
4588            ctx.set_parse_flow_ambiguous(true);
4589            let gc = ctx.lock();
4590            let expr = flow_ambiguous_expr(&gc, &mut sm, b"f<T>();");
4591            let Node::CallExpression(call) = expr else {
4592                panic!("expected CallExpression, got {:?}", expr.kind())
4593            };
4594            assert!(
4595                call.type_arguments.is_some(),
4596                "f<T>() must keep type arguments"
4597            );
4598        }
4599        // a < b — speculation rolled back to a comparison.
4600        {
4601            let mut sm = hermes_support::manager::SourceErrorManager::new();
4602            let mut ctx = Context::new();
4603            ctx.set_parse_flow(true);
4604            ctx.set_parse_flow_ambiguous(true);
4605            let gc = ctx.lock();
4606            let expr = flow_ambiguous_expr(&gc, &mut sm, b"a < b;");
4607            assert!(
4608                matches!(expr, Node::BinaryExpression(_)),
4609                "a < b must be a BinaryExpression, got {:?}",
4610                expr.kind()
4611            );
4612        }
4613    }
4614
4615    /// `new C<T>` (no args) is a `NewExpression` with type-args; `new C<T>(x)`
4616    /// keeps both type-args and arguments.
4617    #[test]
4618    fn flow_new_type_args() {
4619        use hermes_ast::context::Context;
4620        use hermes_ast::node::Node;
4621        // new C<T> — type-args, NO parens required.
4622        {
4623            let mut sm = hermes_support::manager::SourceErrorManager::new();
4624            let mut ctx = Context::new();
4625            ctx.set_parse_flow(true);
4626            ctx.set_parse_flow_ambiguous(true);
4627            let gc = ctx.lock();
4628            let expr = flow_ambiguous_expr(&gc, &mut sm, b"new C<T>;");
4629            let Node::NewExpression(new_expr) = expr else {
4630                panic!("expected NewExpression, got {:?}", expr.kind())
4631            };
4632            assert!(
4633                new_expr.type_arguments.is_some(),
4634                "new C<T> must keep type arguments"
4635            );
4636            assert_eq!(new_expr.arguments.iter().count(), 0, "no args");
4637        }
4638        // new C<T>(x) — type-args AND one argument.
4639        {
4640            let mut sm = hermes_support::manager::SourceErrorManager::new();
4641            let mut ctx = Context::new();
4642            ctx.set_parse_flow(true);
4643            ctx.set_parse_flow_ambiguous(true);
4644            let gc = ctx.lock();
4645            let expr = flow_ambiguous_expr(&gc, &mut sm, b"new C<T>(x);");
4646            let Node::NewExpression(new_expr) = expr else {
4647                panic!("expected NewExpression, got {:?}", expr.kind())
4648            };
4649            assert!(new_expr.type_arguments.is_some(), "type args kept");
4650            assert_eq!(new_expr.arguments.iter().count(), 1, "one arg");
4651        }
4652    }
4653
4654    /// `obj?.foo<T>(x)` is an `OptionalCallExpression` with type-args (the
4655    /// `?.<T>()` form is unambiguous Flow — no SavePoint, no rollback).
4656    #[test]
4657    fn flow_optional_call_type_args() {
4658        use hermes_ast::context::Context;
4659        use hermes_ast::node::Node;
4660        let mut sm = hermes_support::manager::SourceErrorManager::new();
4661        let mut ctx = Context::new();
4662        ctx.set_parse_flow(true);
4663        ctx.set_parse_flow_ambiguous(true);
4664        let gc = ctx.lock();
4665        let expr = flow_ambiguous_expr(&gc, &mut sm, b"obj?.foo<T>(x);");
4666        // obj?.foo is an OptionalMemberExpression; the trailing call is an
4667        // OptionalCallExpression carrying the type arguments.
4668        let Node::OptionalCallExpression(call) = expr else {
4669            panic!("expected OptionalCallExpression, got {:?}", expr.kind())
4670        };
4671        assert!(
4672            call.type_arguments.is_some(),
4673            "obj?.foo<T>(x) must keep type arguments"
4674        );
4675        assert_eq!(call.arguments.iter().count(), 1, "one argument");
4676    }
4677
4678    /// Without the ambiguous flag, `f<T>()` is NOT a type-args call: `f < T`
4679    /// is a comparison, exactly like plain JS. (Guards against Flow leakage.)
4680    #[test]
4681    fn flow_ambiguous_off_keeps_comparison() {
4682        use hermes_ast::context::Context;
4683        use hermes_ast::node::Node;
4684        // parse_flow ON but parse_flow_ambiguous OFF: still a comparison chain.
4685        let mut sm = hermes_support::manager::SourceErrorManager::new();
4686        let mut ctx = Context::new();
4687        ctx.set_parse_flow(true);
4688        // deliberately NOT setting parse_flow_ambiguous.
4689        let gc = ctx.lock();
4690        let expr = flow_ambiguous_expr(&gc, &mut sm, b"f < T > (g);");
4691        // `f < T > (g)` parses as `(f < T) > (g)` — a comparison, not a call.
4692        assert!(
4693            matches!(expr, Node::BinaryExpression(_)),
4694            "without ambiguous flag, f<T>(g) is a comparison, got {:?}",
4695            expr.kind()
4696        );
4697    }
4698
4699    // ----------------------------------------------------------------------
4700    // P6.1: typed arrows + return-type/predicate backtrack + type-cast +
4701    // CoverTypedIdentifier.
4702    // ----------------------------------------------------------------------
4703
4704    /// Lock a Flow context (parse_flow only; the typed-arrow grammar is gated
4705    /// on `parse_flow`, NOT `parse_flow_ambiguous`), parse `src`, return the
4706    /// init of the single top-level `const`.
4707    fn flow_const_init<'gc>(
4708        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4709        sm: &mut hermes_support::manager::SourceErrorManager,
4710        src: &[u8],
4711    ) -> &'gc hermes_ast::node::Node<'gc> {
4712        let stmt = parse_one_stmt(gc, sm, src);
4713        let hermes_ast::node::Node::VariableDeclaration(vd) = stmt else {
4714            panic!("expected VariableDeclaration, got {:?}", stmt.kind())
4715        };
4716        let decl = vd.declarations.iter().next().expect("one declarator");
4717        let hermes_ast::node::Node::VariableDeclarator(d) = decl else {
4718            panic!("expected VariableDeclarator, got {:?}", decl.kind())
4719        };
4720        d.init.expect("declarator has an init")
4721    }
4722
4723    /// `<T>(x: T): T => x` → an ArrowFunctionExpression carrying type
4724    /// parameters, a return type, and `expression == true`.
4725    #[test]
4726    fn flow_typed_arrow_full() {
4727        use hermes_ast::context::Context;
4728        use hermes_ast::node::Node;
4729        let mut sm = hermes_support::manager::SourceErrorManager::new();
4730        let mut ctx = Context::new();
4731        ctx.set_parse_flow(true);
4732        let gc = ctx.lock();
4733        let init =
4734            flow_const_init(&gc, &mut sm, b"const f = <T>(x: T): T => x;");
4735        let Node::ArrowFunctionExpression(arrow) = init else {
4736            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4737        };
4738        assert!(arrow.type_parameters.is_some(), "has type parameters");
4739        assert!(arrow.return_type.is_some(), "has return type");
4740        assert!(arrow.predicate.is_none(), "no predicate");
4741        assert!(arrow.expression.get(), "concise (expression) body");
4742        assert!(!arrow.r#async.get(), "not async");
4743    }
4744
4745    /// `(x): x is number => true` → an arrow with a predicate (type guard) and
4746    /// no return type.
4747    #[test]
4748    fn flow_typed_arrow_predicate() {
4749        use hermes_ast::context::Context;
4750        use hermes_ast::node::Node;
4751        let mut sm = hermes_support::manager::SourceErrorManager::new();
4752        let mut ctx = Context::new();
4753        ctx.set_parse_flow(true);
4754        let gc = ctx.lock();
4755        let init = flow_const_init(
4756            &gc,
4757            &mut sm,
4758            b"const g = (x): x is number => true;",
4759        );
4760        let Node::ArrowFunctionExpression(arrow) = init else {
4761            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4762        };
4763        assert!(arrow.type_parameters.is_none(), "no type parameters");
4764        // `x is number` is a TypePredicate carried as the return_type; the
4765        // separate `predicate` field is only set by the `%checks` form.
4766        let rt = arrow.return_type.expect("predicate sets return_type");
4767        let Node::TypeAnnotation(ta) = rt else {
4768            panic!("return_type wraps a TypeAnnotation, got {:?}", rt.kind())
4769        };
4770        assert!(
4771            matches!(ta.type_annotation, Node::TypePredicate(_)),
4772            "return type is a TypePredicate, got {:?}",
4773            ta.type_annotation.kind()
4774        );
4775        assert!(arrow.predicate.is_none(), "no %checks predicate");
4776    }
4777
4778    /// `(): void => {}` → an arrow with a return type and a block body
4779    /// (`expression == false`).
4780    #[test]
4781    fn flow_typed_arrow_void_block() {
4782        use hermes_ast::context::Context;
4783        use hermes_ast::node::Node;
4784        let mut sm = hermes_support::manager::SourceErrorManager::new();
4785        let mut ctx = Context::new();
4786        ctx.set_parse_flow(true);
4787        let gc = ctx.lock();
4788        let init = flow_const_init(&gc, &mut sm, b"const e = (): void => {};");
4789        let Node::ArrowFunctionExpression(arrow) = init else {
4790            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4791        };
4792        assert!(arrow.return_type.is_some(), "has return type");
4793        assert!(!arrow.expression.get(), "block body");
4794    }
4795
4796    /// `async <T>(x: T): T => x` → an async typed arrow.
4797    #[test]
4798    fn flow_typed_async_arrow() {
4799        use hermes_ast::context::Context;
4800        use hermes_ast::node::Node;
4801        let mut sm = hermes_support::manager::SourceErrorManager::new();
4802        let mut ctx = Context::new();
4803        ctx.set_parse_flow(true);
4804        let gc = ctx.lock();
4805        let init =
4806            flow_const_init(&gc, &mut sm, b"const f = async <T>(x: T): T => x;");
4807        let Node::ArrowFunctionExpression(arrow) = init else {
4808            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4809        };
4810        assert!(arrow.r#async.get(), "is async");
4811        assert!(arrow.type_parameters.is_some(), "has type parameters");
4812        assert!(arrow.return_type.is_some(), "has return type");
4813    }
4814
4815    /// `async (x: number) => x` → an async typed arrow without type params.
4816    #[test]
4817    fn flow_typed_async_arrow_no_generics() {
4818        use hermes_ast::context::Context;
4819        use hermes_ast::node::Node;
4820        let mut sm = hermes_support::manager::SourceErrorManager::new();
4821        let mut ctx = Context::new();
4822        ctx.set_parse_flow(true);
4823        let gc = ctx.lock();
4824        let init =
4825            flow_const_init(&gc, &mut sm, b"const g = async (x: number) => x;");
4826        let Node::ArrowFunctionExpression(arrow) = init else {
4827            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4828        };
4829        assert!(arrow.r#async.get(), "is async");
4830        assert!(arrow.type_parameters.is_none(), "no type parameters");
4831    }
4832
4833    /// `async (x) => x` (no types) is still an async arrow function — the
4834    /// typed-async path falls back to the normal async handling.
4835    #[test]
4836    fn flow_plain_async_arrow_still_works() {
4837        use hermes_ast::context::Context;
4838        use hermes_ast::node::Node;
4839        let mut sm = hermes_support::manager::SourceErrorManager::new();
4840        let mut ctx = Context::new();
4841        ctx.set_parse_flow(true);
4842        let gc = ctx.lock();
4843        let init = flow_const_init(&gc, &mut sm, b"const g = async (x) => x;");
4844        let Node::ArrowFunctionExpression(arrow) = init else {
4845            panic!("expected ArrowFunctionExpression, got {:?}", init.kind())
4846        };
4847        assert!(arrow.r#async.get(), "is async");
4848        assert!(arrow.type_parameters.is_none(), "no type parameters");
4849        assert!(arrow.return_type.is_none(), "no return type");
4850    }
4851
4852    /// `async` used as a plain identifier (not followed by `<`/`(`/an ident on
4853    /// the same line) stays an identifier reference.
4854    #[test]
4855    fn flow_async_as_identifier() {
4856        use hermes_ast::context::Context;
4857        use hermes_ast::node::Node;
4858        let mut sm = hermes_support::manager::SourceErrorManager::new();
4859        let mut ctx = Context::new();
4860        ctx.set_parse_flow(true);
4861        let gc = ctx.lock();
4862        let init = flow_const_init(&gc, &mut sm, b"const a = async;");
4863        assert!(
4864            matches!(init, Node::Identifier(_)),
4865            "bare `async` is an Identifier, got {:?}",
4866            init.kind()
4867        );
4868    }
4869
4870    /// `(x: number)` parenthesized type-cast → `TypeCastExpression`.
4871    #[test]
4872    fn flow_type_cast() {
4873        use hermes_ast::context::Context;
4874        use hermes_ast::node::Node;
4875        let mut sm = hermes_support::manager::SourceErrorManager::new();
4876        let mut ctx = Context::new();
4877        ctx.set_parse_flow(true);
4878        let gc = ctx.lock();
4879        let init = flow_const_init(&gc, &mut sm, b"const a = (x: number);");
4880        let Node::TypeCastExpression(cast) = init else {
4881            panic!("expected TypeCastExpression, got {:?}", init.kind())
4882        };
4883        assert_eq!(ident_bytes(&gc, cast.expression), b"x");
4884        assert!(
4885            matches!(cast.type_annotation, Node::TypeAnnotation(_)),
4886            "type is wrapped in a TypeAnnotation, got {:?}",
4887            cast.type_annotation.kind()
4888        );
4889    }
4890
4891    /// `({p}: O)` object-pattern type-cast → `TypeCastExpression` whose
4892    /// expression is an ObjectExpression.
4893    #[test]
4894    fn flow_type_cast_object() {
4895        use hermes_ast::context::Context;
4896        use hermes_ast::node::Node;
4897        let mut sm = hermes_support::manager::SourceErrorManager::new();
4898        let mut ctx = Context::new();
4899        ctx.set_parse_flow(true);
4900        let gc = ctx.lock();
4901        let init = flow_const_init(&gc, &mut sm, b"const b = ({p}: O);");
4902        let Node::TypeCastExpression(cast) = init else {
4903            panic!("expected TypeCastExpression, got {:?}", init.kind())
4904        };
4905        assert!(
4906            matches!(cast.expression, Node::ObjectExpression(_)),
4907            "expr is ObjectExpression, got {:?}",
4908            cast.expression.kind()
4909        );
4910    }
4911
4912    /// `cond ? (a: T) => a : b` — the conditional-consequent cover: the typed
4913    /// arrow in the consequent must be recognized through the backtracking.
4914    #[test]
4915    fn flow_conditional_consequent_cover() {
4916        use hermes_ast::context::Context;
4917        use hermes_ast::node::Node;
4918        let mut sm = hermes_support::manager::SourceErrorManager::new();
4919        let mut ctx = Context::new();
4920        ctx.set_parse_flow(true);
4921        let gc = ctx.lock();
4922        let init =
4923            flow_const_init(&gc, &mut sm, b"const c = cond ? (a: T) => a : b;");
4924        let Node::ConditionalExpression(cond) = init else {
4925            panic!("expected ConditionalExpression, got {:?}", init.kind())
4926        };
4927        assert!(
4928            matches!(cond.consequent, Node::ArrowFunctionExpression(_)),
4929            "consequent is a typed arrow, got {:?}",
4930            cond.consequent.kind()
4931        );
4932        assert_eq!(ident_bytes(&gc, cond.alternate), b"b");
4933    }
4934
4935    /// CRITICAL disambiguation: with `parse_flow` on, `(a < b, c > (d))` must
4936    /// still parse as a SequenceExpression of comparisons (NOT a typed arrow).
4937    /// The `<T>(…) =>` head must roll back when there is no `=>`.
4938    #[test]
4939    fn flow_typed_arrow_disambiguation_comparison() {
4940        use hermes_ast::context::Context;
4941        use hermes_ast::node::Node;
4942        let mut sm = hermes_support::manager::SourceErrorManager::new();
4943        let mut ctx = Context::new();
4944        ctx.set_parse_flow(true);
4945        let gc = ctx.lock();
4946        let init =
4947            flow_const_init(&gc, &mut sm, b"const r = (a < b, c > (d));");
4948        // `(a < b, c > (d))` → a parenthesized SequenceExpression of two
4949        // BinaryExpressions; it must NOT have been eaten as an arrow head.
4950        assert!(
4951            matches!(init, Node::SequenceExpression(_)),
4952            "comparison sequence, got {:?}",
4953            init.kind()
4954        );
4955    }
4956
4957    /// `a < b` at an assignment position (where a typed arrow could start) with
4958    /// `parse_flow` on still parses as a comparison.
4959    #[test]
4960    fn flow_typed_arrow_lt_not_arrow() {
4961        use hermes_ast::context::Context;
4962        use hermes_ast::node::Node;
4963        let mut sm = hermes_support::manager::SourceErrorManager::new();
4964        let mut ctx = Context::new();
4965        ctx.set_parse_flow(true);
4966        let gc = ctx.lock();
4967        let init = flow_const_init(&gc, &mut sm, b"const r = a < b;");
4968        assert!(
4969            matches!(init, Node::BinaryExpression(_)),
4970            "a < b is a comparison, got {:?}",
4971            init.kind()
4972        );
4973    }
4974
4975    // P5.1: the full Flow type-annotation hierarchy (js/flow/).
4976
4977    /// Helper: parse `src` with the caller's (Flow-enabled) context and
4978    /// return the right-hand side of the single top-level `TypeAlias`.
4979    fn flow_alias_right<'gc>(
4980        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4981        sm: &mut hermes_support::manager::SourceErrorManager,
4982        src: &[u8],
4983    ) -> &'gc hermes_ast::node::Node<'gc> {
4984        let stmt = parse_one_stmt(gc, sm, src);
4985        let hermes_ast::node::Node::TypeAlias(alias) = stmt else {
4986            panic!("expected TypeAlias, got {:?}", stmt.kind())
4987        };
4988        alias.right
4989    }
4990
4991    /// Helper: assert `node` is a `GenericTypeAnnotation` over a plain
4992    /// `Identifier` named `name` (the shape every bare `X` parses to).
4993    fn assert_generic_named<'gc>(
4994        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
4995        node: &hermes_ast::node::Node<'gc>,
4996        name: &[u8],
4997    ) {
4998        use hermes_ast::node::Node;
4999        let Node::GenericTypeAnnotation(g) = node else {
5000            panic!("expected GenericTypeAnnotation, got {:?}", node.kind())
5001        };
5002        assert!(g.type_parameters.is_none(), "no type args");
5003        assert_eq!(ident_bytes(gc, g.id), name);
5004    }
5005
5006    /// Unions and intersections: member counts, leading-separator
5007    /// equivalence, and `&` binding tighter than `|`.
5008    #[test]
5009    fn flow_union_intersection_types() {
5010        use hermes_ast::context::Context;
5011        use hermes_ast::node::Node;
5012        let mut sm = hermes_support::manager::SourceErrorManager::new();
5013        let mut ctx = Context::new();
5014        ctx.set_parse_flow(true);
5015        let gc = ctx.lock();
5016
5017        // `X | Y | Z` → a single Union with three members.
5018        let ty = flow_alias_right(&gc, &mut sm, b"type A = X | Y | Z;");
5019        let Node::UnionTypeAnnotation(u) = ty else {
5020            panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
5021        };
5022        let members: Vec<_> = u.types.iter().collect();
5023        assert_eq!(members.len(), 3);
5024        assert_generic_named(&gc, members[0], b"X");
5025        assert_generic_named(&gc, members[2], b"Z");
5026
5027        // A leading `|` is allowed and does not add a member.
5028        let ty = flow_alias_right(&gc, &mut sm, b"type A = | X | Y;");
5029        let Node::UnionTypeAnnotation(u) = ty else {
5030            panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
5031        };
5032        assert_eq!(u.types.iter().count(), 2);
5033
5034        // ...but a sole leading `|` yields the bare element, not a union.
5035        let ty = flow_alias_right(&gc, &mut sm, b"type A = | X;");
5036        assert_generic_named(&gc, ty, b"X");
5037
5038        // `X & Y | Z` → Union[Intersection[X, Y], Z].
5039        let ty = flow_alias_right(&gc, &mut sm, b"type A = X & Y | Z;");
5040        let Node::UnionTypeAnnotation(u) = ty else {
5041            panic!("expected UnionTypeAnnotation, got {:?}", ty.kind())
5042        };
5043        let members: Vec<_> = u.types.iter().collect();
5044        assert_eq!(members.len(), 2);
5045        let Node::IntersectionTypeAnnotation(i) = members[0] else {
5046            panic!(
5047                "expected IntersectionTypeAnnotation, got {:?}",
5048                members[0].kind()
5049            )
5050        };
5051        assert_eq!(i.types.iter().count(), 2);
5052        assert_generic_named(&gc, members[1], b"Z");
5053    }
5054
5055    /// `??X` parses as two nested NullableTypeAnnotations.
5056    #[test]
5057    fn flow_nullable_nesting() {
5058        use hermes_ast::context::Context;
5059        use hermes_ast::node::Node;
5060        let mut sm = hermes_support::manager::SourceErrorManager::new();
5061        let mut ctx = Context::new();
5062        ctx.set_parse_flow(true);
5063        let gc = ctx.lock();
5064
5065        let ty = flow_alias_right(&gc, &mut sm, b"type A = ??X;");
5066        let Node::NullableTypeAnnotation(outer) = ty else {
5067            panic!("expected NullableTypeAnnotation, got {:?}", ty.kind())
5068        };
5069        let Node::NullableTypeAnnotation(inner) = outer.type_annotation else {
5070            panic!(
5071                "expected nested NullableTypeAnnotation, got {:?}",
5072                outer.type_annotation.kind()
5073            )
5074        };
5075        assert_generic_named(&gc, inner.type_annotation, b"X");
5076    }
5077
5078    /// Postfix types: `X[]`/`X[][]` arrays, `X[K]` indexed access, `X?.[K]`
5079    /// optional indexed access, and the stickiness of `?.` in `X?.[A][B]`.
5080    #[test]
5081    fn flow_postfix_types() {
5082        use hermes_ast::context::Context;
5083        use hermes_ast::node::Node;
5084        let mut sm = hermes_support::manager::SourceErrorManager::new();
5085        let mut ctx = Context::new();
5086        ctx.set_parse_flow(true);
5087        let gc = ctx.lock();
5088
5089        // `X[][]` → Array(Array(X)).
5090        let ty = flow_alias_right(&gc, &mut sm, b"type A = X[][];");
5091        let Node::ArrayTypeAnnotation(outer) = ty else {
5092            panic!("expected ArrayTypeAnnotation, got {:?}", ty.kind())
5093        };
5094        let Node::ArrayTypeAnnotation(inner) = outer.element_type else {
5095            panic!(
5096                "expected nested ArrayTypeAnnotation, got {:?}",
5097                outer.element_type.kind()
5098            )
5099        };
5100        assert_generic_named(&gc, inner.element_type, b"X");
5101
5102        // `X[K]` → IndexedAccessType.
5103        let ty = flow_alias_right(&gc, &mut sm, b"type A = X[K];");
5104        let Node::IndexedAccessType(idx) = ty else {
5105            panic!("expected IndexedAccessType, got {:?}", ty.kind())
5106        };
5107        assert_generic_named(&gc, idx.object_type, b"X");
5108        assert_generic_named(&gc, idx.index_type, b"K");
5109
5110        // `X?.[K]` → OptionalIndexedAccessType with optional=true.
5111        let ty = flow_alias_right(&gc, &mut sm, b"type A = X?.[K];");
5112        let Node::OptionalIndexedAccessType(opt) = ty else {
5113            panic!("expected OptionalIndexedAccessType, got {:?}", ty.kind())
5114        };
5115        assert!(opt.optional.get(), "?.[ access is optional");
5116
5117        // `X?.[A][B]`: once a `?.[` is seen, the enclosing plain `[B]` access
5118        // is also an OptionalIndexedAccessType, but with optional=false.
5119        let ty = flow_alias_right(&gc, &mut sm, b"type A = X?.[A][B];");
5120        let Node::OptionalIndexedAccessType(outer) = ty else {
5121            panic!("expected OptionalIndexedAccessType, got {:?}", ty.kind())
5122        };
5123        assert!(!outer.optional.get(), "[B] itself is not optional");
5124        let Node::OptionalIndexedAccessType(inner) = outer.object_type else {
5125            panic!(
5126                "expected inner OptionalIndexedAccessType, got {:?}",
5127                outer.object_type.kind()
5128            )
5129        };
5130        assert!(inner.optional.get(), "?.[A] is optional");
5131    }
5132
5133    /// Generic types: qualified names are left-associated
5134    /// QualifiedTypeIdentifiers; `Foo<>` is an empty (but present)
5135    /// TypeParameterInstantiation.
5136    #[test]
5137    fn flow_generic_types() {
5138        use hermes_ast::context::Context;
5139        use hermes_ast::node::Node;
5140        let mut sm = hermes_support::manager::SourceErrorManager::new();
5141        let mut ctx = Context::new();
5142        ctx.set_parse_flow(true);
5143        let gc = ctx.lock();
5144
5145        // `Foo.Bar.Baz` → Qualified(Qualified(Foo, Bar), Baz).
5146        let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo.Bar.Baz;");
5147        let Node::GenericTypeAnnotation(g) = ty else {
5148            panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
5149        };
5150        assert!(g.type_parameters.is_none());
5151        let Node::QualifiedTypeIdentifier(outer) = g.id else {
5152            panic!("expected QualifiedTypeIdentifier, got {:?}", g.id.kind())
5153        };
5154        assert_eq!(ident_bytes(&gc, outer.id), b"Baz");
5155        let Node::QualifiedTypeIdentifier(inner) = outer.qualification else {
5156            panic!(
5157                "expected inner QualifiedTypeIdentifier, got {:?}",
5158                outer.qualification.kind()
5159            )
5160        };
5161        assert_eq!(ident_bytes(&gc, inner.qualification), b"Foo");
5162        assert_eq!(ident_bytes(&gc, inner.id), b"Bar");
5163
5164        // `Foo<>` → empty TypeParameterInstantiation.
5165        let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<>;");
5166        let Node::GenericTypeAnnotation(g) = ty else {
5167            panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
5168        };
5169        let args = g.type_parameters.expect("has type args");
5170        let Node::TypeParameterInstantiation(inst) = args else {
5171            panic!("expected TypeParameterInstantiation, got {:?}", args.kind())
5172        };
5173        assert_eq!(inst.params.iter().count(), 0, "`Foo<>` has no args");
5174
5175        // `Foo<X, Y>` → two args.
5176        let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<X, Y>;");
5177        let Node::GenericTypeAnnotation(g) = ty else {
5178            panic!("expected GenericTypeAnnotation, got {:?}", ty.kind())
5179        };
5180        let Node::TypeParameterInstantiation(inst) =
5181            g.type_parameters.expect("has type args")
5182        else {
5183            panic!("expected TypeParameterInstantiation")
5184        };
5185        assert_eq!(inst.params.iter().count(), 2);
5186    }
5187
5188    /// Nested generic type args: the closing `>` of inner type args must be
5189    /// consumed with GrammarContext::Type (the C++ default for
5190    /// `parseTypeArgsFlow`, JSParserImpl.h:1506) so the lexer splits the
5191    /// following `>>` into two `>` tokens instead of one shift token.
5192    #[test]
5193    fn flow_nested_generic_type_args() {
5194        use hermes_ast::context::Context;
5195        use hermes_ast::node::Node;
5196        let mut sm = hermes_support::manager::SourceErrorManager::new();
5197        let mut ctx = Context::new();
5198        ctx.set_parse_flow(true);
5199        let gc = ctx.lock();
5200
5201        // `Foo<Bar<Baz<U>>>` → three nested GenericTypeAnnotation levels,
5202        // each with exactly one type argument.
5203        let ty = flow_alias_right(&gc, &mut sm, b"type A = Foo<Bar<Baz<U>>>;");
5204        let mut node = ty;
5205        for name in [&b"Foo"[..], b"Bar", b"Baz"] {
5206            let Node::GenericTypeAnnotation(g) = node else {
5207                panic!("expected GenericTypeAnnotation, got {:?}", node.kind())
5208            };
5209            assert_eq!(ident_bytes(&gc, g.id), name);
5210            let Node::TypeParameterInstantiation(inst) =
5211                g.type_parameters.expect("has type args")
5212            else {
5213                panic!("expected TypeParameterInstantiation")
5214            };
5215            assert_eq!(inst.params.iter().count(), 1, "one arg at each level");
5216            node = inst.params.iter().next().unwrap();
5217        }
5218        // Innermost argument: a bare `U` with no type args.
5219        assert_generic_named(&gc, node, b"U");
5220    }
5221
5222    /// Typeof types: qualified chains, wrapping parens (recorded on the
5223    /// argument's parens counter — invisible in the AST dump), type args.
5224    #[test]
5225    fn flow_typeof_types() {
5226        use hermes_ast::context::Context;
5227        use hermes_ast::node::Node;
5228        let mut sm = hermes_support::manager::SourceErrorManager::new();
5229        let mut ctx = Context::new();
5230        ctx.set_parse_flow(true);
5231        let gc = ctx.lock();
5232
5233        // `typeof x.y` → argument is a QualifiedTypeofIdentifier.
5234        let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof x.y;");
5235        let Node::TypeofTypeAnnotation(t) = ty else {
5236            panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
5237        };
5238        assert!(t.type_arguments.is_none());
5239        let Node::QualifiedTypeofIdentifier(q) = t.argument else {
5240            panic!(
5241                "expected QualifiedTypeofIdentifier, got {:?}",
5242                t.argument.kind()
5243            )
5244        };
5245        assert_eq!(ident_bytes(&gc, q.qualification), b"x");
5246        assert_eq!(ident_bytes(&gc, q.id), b"y");
5247
5248        // `typeof (x)` → the paren is recorded on the Identifier argument.
5249        let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof (x);");
5250        let Node::TypeofTypeAnnotation(t) = ty else {
5251            panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
5252        };
5253        assert!(matches!(t.argument, Node::Identifier(_)));
5254        assert_eq!(t.argument.metadata().parens.get(), 1, "one paren recorded");
5255
5256        // `typeof x<Y>` → type arguments attached to the TypeofTypeAnnotation.
5257        let ty = flow_alias_right(&gc, &mut sm, b"type A = typeof x<Y>;");
5258        let Node::TypeofTypeAnnotation(t) = ty else {
5259            panic!("expected TypeofTypeAnnotation, got {:?}", ty.kind())
5260        };
5261        let Node::TypeParameterInstantiation(inst) =
5262            t.type_arguments.expect("has type args")
5263        else {
5264            panic!("expected TypeParameterInstantiation")
5265        };
5266        assert_eq!(inst.params.iter().count(), 1);
5267    }
5268
5269    /// Tuple types: plain, labeled (with optional), spread (bare and
5270    /// labeled), variance prefixes, inexact `...`, and empty.
5271    #[test]
5272    fn flow_tuple_types() {
5273        use hermes_ast::context::Context;
5274        use hermes_ast::node::Node;
5275        let mut sm = hermes_support::manager::SourceErrorManager::new();
5276        let mut ctx = Context::new();
5277        ctx.set_parse_flow(true);
5278        let gc = ctx.lock();
5279
5280        // `[X, Y]` → two unlabeled (bare type) elements, not inexact.
5281        let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, Y];");
5282        let Node::TupleTypeAnnotation(t) = ty else {
5283            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5284        };
5285        assert!(!t.inexact.get());
5286        let elems: Vec<_> = t.element_types.iter().collect();
5287        assert_eq!(elems.len(), 2);
5288        assert_generic_named(&gc, elems[0], b"X");
5289
5290        // `[a: X, b?: Y]` → labeled elements; the second is optional.
5291        let ty = flow_alias_right(&gc, &mut sm, b"type A = [a: X, b?: Y];");
5292        let Node::TupleTypeAnnotation(t) = ty else {
5293            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5294        };
5295        let elems: Vec<_> = t.element_types.iter().collect();
5296        assert_eq!(elems.len(), 2);
5297        let Node::TupleTypeLabeledElement(first) = elems[0] else {
5298            panic!(
5299                "expected TupleTypeLabeledElement, got {:?}",
5300                elems[0].kind()
5301            )
5302        };
5303        assert_eq!(ident_bytes(&gc, first.label), b"a");
5304        assert!(!first.optional.get());
5305        assert!(first.variance.is_none());
5306        let Node::TupleTypeLabeledElement(second) = elems[1] else {
5307            panic!(
5308                "expected TupleTypeLabeledElement, got {:?}",
5309                elems[1].kind()
5310            )
5311        };
5312        assert!(second.optional.get(), "b? is optional");
5313
5314        // `[X, ...Y]` → bare spread (no label); `[...rest: Y]` → labeled.
5315        let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, ...Y];");
5316        let Node::TupleTypeAnnotation(t) = ty else {
5317            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5318        };
5319        let elems: Vec<_> = t.element_types.iter().collect();
5320        let Node::TupleTypeSpreadElement(spread) = elems[1] else {
5321            panic!(
5322                "expected TupleTypeSpreadElement, got {:?}",
5323                elems[1].kind()
5324            )
5325        };
5326        assert!(spread.label.is_none());
5327        assert_generic_named(&gc, spread.type_annotation, b"Y");
5328
5329        let ty = flow_alias_right(&gc, &mut sm, b"type A = [...rest: Y];");
5330        let Node::TupleTypeAnnotation(t) = ty else {
5331            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5332        };
5333        let elems: Vec<_> = t.element_types.iter().collect();
5334        let Node::TupleTypeSpreadElement(spread) = elems[0] else {
5335            panic!(
5336                "expected TupleTypeSpreadElement, got {:?}",
5337                elems[0].kind()
5338            )
5339        };
5340        assert_eq!(ident_bytes(&gc, spread.label.expect("labeled")), b"rest");
5341
5342        // `[+a: X, -b: Y]` → Variance kinds "plus" / "minus".
5343        let ty = flow_alias_right(&gc, &mut sm, b"type A = [+a: X, -b: Y];");
5344        let Node::TupleTypeAnnotation(t) = ty else {
5345            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5346        };
5347        let elems: Vec<_> = t.element_types.iter().collect();
5348        let Node::TupleTypeLabeledElement(first) = elems[0] else {
5349            panic!("expected TupleTypeLabeledElement")
5350        };
5351        let Node::Variance(v) = first.variance.expect("has variance") else {
5352            panic!("expected Variance")
5353        };
5354        assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
5355        let Node::TupleTypeLabeledElement(second) = elems[1] else {
5356            panic!("expected TupleTypeLabeledElement")
5357        };
5358        let Node::Variance(v) = second.variance.expect("has variance") else {
5359            panic!("expected Variance")
5360        };
5361        assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"minus");
5362
5363        // `[X, ...]` → inexact, with one element.
5364        let ty = flow_alias_right(&gc, &mut sm, b"type A = [X, ...];");
5365        let Node::TupleTypeAnnotation(t) = ty else {
5366            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5367        };
5368        assert!(t.inexact.get(), "trailing ... makes the tuple inexact");
5369        assert_eq!(t.element_types.iter().count(), 1);
5370
5371        // `[]` → empty tuple.
5372        let ty = flow_alias_right(&gc, &mut sm, b"type A = [];");
5373        let Node::TupleTypeAnnotation(t) = ty else {
5374            panic!("expected TupleTypeAnnotation, got {:?}", ty.kind())
5375        };
5376        assert_eq!(t.element_types.iter().count(), 0);
5377        assert!(!t.inexact.get());
5378    }
5379
5380    /// The two tuple-specific diagnostics keep the exact C++ texts, and a
5381    /// non-identifier label trips the reparse helper's "identifier expected".
5382    #[test]
5383    fn flow_tuple_errors() {
5384        use hermes_ast::context::Context;
5385        use hermes_support::diag::{CollectingHandler, DiagKind};
5386        use hermes_support::manager::SourceErrorManager;
5387
5388        // Comma after the inexact `...`.
5389        {
5390            let mut sm = SourceErrorManager::new();
5391            let mut ctx = Context::new();
5392            ctx.set_parse_flow(true);
5393            let gc = ctx.lock();
5394            let atoms = &gc.ctx().atom_table;
5395            let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [X, ..., Y];");
5396            let h = sm.handler_as::<CollectingHandler>().unwrap();
5397            assert!(
5398                h.messages().iter().any(|m| m.kind == DiagKind::Error
5399                    && m.message
5400                        == "trailing commas after inexact tuple types are not allowed"),
5401                "got {:?}",
5402                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
5403            );
5404        }
5405
5406        // Variance on an unlabeled element.
5407        {
5408            let mut sm = SourceErrorManager::new();
5409            let mut ctx = Context::new();
5410            ctx.set_parse_flow(true);
5411            let gc = ctx.lock();
5412            let atoms = &gc.ctx().atom_table;
5413            let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [+X];");
5414            let h = sm.handler_as::<CollectingHandler>().unwrap();
5415            assert!(
5416                h.messages().iter().any(|m| m.kind == DiagKind::Error
5417                    && m.message
5418                        == "Variance can only be used with labeled tuple elements"),
5419                "got {:?}",
5420                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
5421            );
5422        }
5423
5424        // A label that cannot reparse as an identifier.
5425        {
5426            let mut sm = SourceErrorManager::new();
5427            let mut ctx = Context::new();
5428            ctx.set_parse_flow(true);
5429            let gc = ctx.lock();
5430            let atoms = &gc.ctx().atom_table;
5431            let _ = parse_with_collector(&gc, &mut sm, atoms, b"type A = [1: X];");
5432            let h = sm.handler_as::<CollectingHandler>().unwrap();
5433            assert!(
5434                h.messages().iter().any(|m| m.kind == DiagKind::Error
5435                    && m.message == "identifier expected"),
5436                "got {:?}",
5437                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
5438            );
5439        }
5440    }
5441
5442    /// `keyof X` → KeyofTypeAnnotation over the generic argument.
5443    #[test]
5444    fn flow_keyof_type() {
5445        use hermes_ast::context::Context;
5446        use hermes_ast::node::Node;
5447        let mut sm = hermes_support::manager::SourceErrorManager::new();
5448        let mut ctx = Context::new();
5449        ctx.set_parse_flow(true);
5450        let gc = ctx.lock();
5451
5452        let ty = flow_alias_right(&gc, &mut sm, b"type A = keyof X;");
5453        let Node::KeyofTypeAnnotation(k) = ty else {
5454            panic!("expected KeyofTypeAnnotation, got {:?}", ty.kind())
5455        };
5456        assert_generic_named(&gc, k.argument, b"X");
5457    }
5458
5459    /// `X extends Y ? A : B` → ConditionalTypeAnnotation with the four
5460    /// generic children in the right slots.
5461    #[test]
5462    fn flow_conditional_type() {
5463        use hermes_ast::context::Context;
5464        use hermes_ast::node::Node;
5465        let mut sm = hermes_support::manager::SourceErrorManager::new();
5466        let mut ctx = Context::new();
5467        ctx.set_parse_flow(true);
5468        let gc = ctx.lock();
5469
5470        let ty =
5471            flow_alias_right(&gc, &mut sm, b"type T = X extends Y ? A : B;");
5472        let Node::ConditionalTypeAnnotation(c) = ty else {
5473            panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
5474        };
5475        assert_generic_named(&gc, c.check_type, b"X");
5476        assert_generic_named(&gc, c.extends_type, b"Y");
5477        assert_generic_named(&gc, c.true_type, b"A");
5478        assert_generic_named(&gc, c.false_type, b"B");
5479    }
5480
5481    /// Infer types: a bound after `extends` is kept inside a conditional's
5482    /// extends clause (conditional types disallowed there), but backtracked
5483    /// away when a `?` follows in a position that allows conditional types.
5484    #[test]
5485    fn flow_infer_type_bound_and_backtrack() {
5486        use hermes_ast::context::Context;
5487        use hermes_ast::node::Node;
5488        let mut sm = hermes_support::manager::SourceErrorManager::new();
5489        let mut ctx = Context::new();
5490        ctx.set_parse_flow(true);
5491        let gc = ctx.lock();
5492
5493        // Helper: unwrap InferTypeAnnotation → TypeParameter.
5494        fn infer_param<'gc>(node: &'gc Node<'gc>) -> &'gc hermes_ast::node::TypeParameter<'gc> {
5495            let Node::InferTypeAnnotation(i) = node else {
5496                panic!("expected InferTypeAnnotation, got {:?}", node.kind())
5497            };
5498            let Node::TypeParameter(p) = i.type_parameter else {
5499                panic!(
5500                    "expected TypeParameter, got {:?}",
5501                    i.type_parameter.kind()
5502                )
5503            };
5504            p
5505        }
5506
5507        // `X extends infer U ? U : never` → infer without bound.
5508        let ty = flow_alias_right(
5509            &gc,
5510            &mut sm,
5511            b"type T = X extends infer U ? U : never;",
5512        );
5513        let Node::ConditionalTypeAnnotation(c) = ty else {
5514            panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
5515        };
5516        let p = infer_param(c.extends_type);
5517        assert_eq!(gc.ctx().atom_table.bytes(p.name.get()), b"U");
5518        assert!(p.bound.is_none());
5519        assert!(p.uses_extends_bound.get());
5520
5521        // `X extends infer U extends V ? U : never`: inside the conditional's
5522        // extends clause conditional types are disallowed, so `extends V`
5523        // binds to the infer type (the bound is KEPT).
5524        let ty = flow_alias_right(
5525            &gc,
5526            &mut sm,
5527            b"type T = X extends infer U extends V ? U : never;",
5528        );
5529        let Node::ConditionalTypeAnnotation(c) = ty else {
5530            panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
5531        };
5532        let p = infer_param(c.extends_type);
5533        let bound = p.bound.expect("bound kept");
5534        assert_generic_named(&gc, bound, b"V");
5535
5536        // `infer U extends V ? A : B` at the top of an annotation: here
5537        // conditional types ARE allowed, so seeing `?` after the speculative
5538        // bound parse backtracks — `extends V` belongs to the conditional and
5539        // the infer loses its bound.
5540        let ty = flow_alias_right(
5541            &gc,
5542            &mut sm,
5543            b"type T = infer U extends V ? A : B;",
5544        );
5545        let Node::ConditionalTypeAnnotation(c) = ty else {
5546            panic!("expected ConditionalTypeAnnotation, got {:?}", ty.kind())
5547        };
5548        let p = infer_param(c.check_type);
5549        assert!(p.bound.is_none(), "bound backtracked away");
5550        assert_generic_named(&gc, c.extends_type, b"V");
5551
5552        // Without a following `?` the bound is kept even at the top level.
5553        let ty =
5554            flow_alias_right(&gc, &mut sm, b"type T = infer U extends V;");
5555        let p = infer_param(ty);
5556        assert!(p.bound.is_some(), "no `?` follows — bound kept");
5557    }
5558
5559    /// Negative literal types: the value is negated and the raw spans the
5560    /// `-` through the literal.
5561    #[test]
5562    fn flow_negative_literal_types() {
5563        use hermes_ast::context::Context;
5564        use hermes_ast::node::Node;
5565        let mut sm = hermes_support::manager::SourceErrorManager::new();
5566        let mut ctx = Context::new();
5567        ctx.set_parse_flow(true);
5568        let gc = ctx.lock();
5569
5570        let ty = flow_alias_right(&gc, &mut sm, b"type A = -3;");
5571        let Node::NumberLiteralTypeAnnotation(n) = ty else {
5572            panic!(
5573                "expected NumberLiteralTypeAnnotation, got {:?}",
5574                ty.kind()
5575            )
5576        };
5577        assert_eq!(n.value.get(), -3.0);
5578        assert_eq!(gc.ctx().atom_table.bytes(n.raw.get()), b"-3");
5579
5580        let ty = flow_alias_right(&gc, &mut sm, b"type A = -2n;");
5581        let Node::BigIntLiteralTypeAnnotation(b) = ty else {
5582            panic!(
5583                "expected BigIntLiteralTypeAnnotation, got {:?}",
5584                ty.kind()
5585            )
5586        };
5587        assert_eq!(gc.ctx().atom_table.bytes(b.raw.get()), b"-2n");
5588    }
5589
5590    // P5.2: function types, object types, type-parameter declarations,
5591    // variance, predicates, return-type annotations (js/flow/).
5592
5593    /// Helper: assert `node` is a `FunctionTypeAnnotation` and return it.
5594    fn as_fta<'gc, 'n>(
5595        node: &'n hermes_ast::node::Node<'gc>,
5596    ) -> &'n hermes_ast::node::FunctionTypeAnnotation<'gc> {
5597        let hermes_ast::node::Node::FunctionTypeAnnotation(fta) = node else {
5598            panic!("expected FunctionTypeAnnotation, got {:?}", node.kind())
5599        };
5600        fta
5601    }
5602
5603    /// Helper: assert `node` is a `FunctionTypeParam` and return it.
5604    fn as_ftp<'gc, 'n>(
5605        node: &'n hermes_ast::node::Node<'gc>,
5606    ) -> &'n hermes_ast::node::FunctionTypeParam<'gc> {
5607        let hermes_ast::node::Node::FunctionTypeParam(ftp) = node else {
5608            panic!("expected FunctionTypeParam, got {:?}", node.kind())
5609        };
5610        ftp
5611    }
5612
5613    /// The full function-type shape: type params, `this` constraint, named/
5614    /// optional params, rest, and the return type.
5615    #[test]
5616    fn flow_function_type_full_shape() {
5617        use hermes_ast::context::Context;
5618        use hermes_ast::node::Node;
5619        let mut sm = hermes_support::manager::SourceErrorManager::new();
5620        let mut ctx = Context::new();
5621        ctx.set_parse_flow(true);
5622        let gc = ctx.lock();
5623
5624        let ty = flow_alias_right(
5625            &gc,
5626            &mut sm,
5627            b"type A = <T>(this: X, a: B, c?: D, ...rest: E) => R;",
5628        );
5629        let fta = as_fta(ty);
5630
5631        // Type parameters.
5632        let tp = fta.type_parameters.expect("has type params");
5633        let Node::TypeParameterDeclaration(tpd) = tp else {
5634            panic!("expected TypeParameterDeclaration, got {:?}", tp.kind())
5635        };
5636        assert_eq!(tpd.params.iter().count(), 1);
5637
5638        // `this` constraint: an unnamed FunctionTypeParam.
5639        let this_param = as_ftp(fta.this.expect("has this constraint"));
5640        assert!(this_param.name.is_none(), "this constraint has no name");
5641        assert_generic_named(&gc, this_param.type_annotation, b"X");
5642
5643        // Named + optional params.
5644        let params: Vec<_> = fta.params.iter().collect();
5645        assert_eq!(params.len(), 2);
5646        let a = as_ftp(params[0]);
5647        assert_eq!(ident_bytes(&gc, a.name.expect("a named")), b"a");
5648        assert!(!a.optional.get());
5649        let c = as_ftp(params[1]);
5650        assert_eq!(ident_bytes(&gc, c.name.expect("c named")), b"c");
5651        assert!(c.optional.get());
5652
5653        // Rest param.
5654        let rest = as_ftp(fta.rest.expect("has rest"));
5655        assert_eq!(ident_bytes(&gc, rest.name.expect("rest named")), b"rest");
5656
5657        // Return type.
5658        assert_generic_named(&gc, fta.return_type, b"R");
5659
5660        // An unnamed parameter type: `(number) => string`.
5661        let ty = flow_alias_right(&gc, &mut sm, b"type C = (number) => string;");
5662        let fta = as_fta(ty);
5663        assert!(fta.this.is_none());
5664        assert!(fta.rest.is_none());
5665        assert!(fta.type_parameters.is_none());
5666        let params: Vec<_> = fta.params.iter().collect();
5667        assert_eq!(params.len(), 1);
5668        let p = as_ftp(params[0]);
5669        assert!(p.name.is_none(), "bare type param has no name");
5670        assert!(matches!(p.type_annotation, Node::NumberTypeAnnotation(_)));
5671    }
5672
5673    /// `(T)` group vs `(x: T) => R` vs `(T) => R` disambiguation; the group
5674    /// returns the inner type with its paren count bumped.
5675    #[test]
5676    fn flow_group_vs_function_type() {
5677        use hermes_ast::context::Context;
5678        let mut sm = hermes_support::manager::SourceErrorManager::new();
5679        let mut ctx = Context::new();
5680        ctx.set_parse_flow(true);
5681        let gc = ctx.lock();
5682
5683        // A plain group: the inner type itself, parens incremented.
5684        let ty = flow_alias_right(&gc, &mut sm, b"type A = (X);");
5685        assert_generic_named(&gc, ty, b"X");
5686        assert_eq!(ty.metadata().parens.get(), 1, "group bumps parens");
5687
5688        // A named param forces a function type.
5689        let ty = flow_alias_right(&gc, &mut sm, b"type B = (x: X) => R;");
5690        let fta = as_fta(ty);
5691        let params: Vec<_> = fta.params.iter().collect();
5692        assert_eq!(ident_bytes(&gc, as_ftp(params[0]).name.unwrap()), b"x");
5693
5694        // An unnamed param resolved as a function by the trailing `=>`.
5695        let ty = flow_alias_right(&gc, &mut sm, b"type C = (X) => R;");
5696        let fta = as_fta(ty);
5697        assert!(as_ftp(fta.params.iter().next().unwrap()).name.is_none());
5698
5699        // An empty param list is always a function.
5700        let ty = flow_alias_right(&gc, &mut sm, b"type D = () => R;");
5701        assert!(as_fta(ty).params.is_empty());
5702    }
5703
5704    /// The anonymous function type `T => U => V` nests to the right.
5705    #[test]
5706    fn flow_anon_function_type() {
5707        use hermes_ast::context::Context;
5708        let mut sm = hermes_support::manager::SourceErrorManager::new();
5709        let mut ctx = Context::new();
5710        ctx.set_parse_flow(true);
5711        let gc = ctx.lock();
5712
5713        let ty = flow_alias_right(&gc, &mut sm, b"type A = T => U => V;");
5714        let outer = as_fta(ty);
5715        let param = as_ftp(outer.params.iter().next().expect("one param"));
5716        assert!(param.name.is_none());
5717        assert_generic_named(&gc, param.type_annotation, b"T");
5718        let inner = as_fta(outer.return_type);
5719        assert_generic_named(&gc, inner.return_type, b"V");
5720    }
5721
5722    /// Object types: plain/optional/method/get/set properties with their
5723    /// `kind` atoms and variance.
5724    #[test]
5725    fn flow_object_type_properties() {
5726        use hermes_ast::context::Context;
5727        use hermes_ast::node::Node;
5728        let mut sm = hermes_support::manager::SourceErrorManager::new();
5729        let mut ctx = Context::new();
5730        ctx.set_parse_flow(true);
5731        let gc = ctx.lock();
5732
5733        let ty = flow_alias_right(
5734            &gc,
5735            &mut sm,
5736            b"type A = { x: B, y?: C, m(): D, get g(): E, set s(v: F): void, +ro: G };",
5737        );
5738        let Node::ObjectTypeAnnotation(obj) = ty else {
5739            panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
5740        };
5741        assert!(!obj.exact.get());
5742        assert!(!obj.inexact.get());
5743        assert!(obj.indexers.is_empty());
5744        assert!(obj.call_properties.is_empty());
5745        assert!(obj.internal_slots.is_empty());
5746
5747        let props: Vec<_> = obj.properties.iter().collect();
5748        assert_eq!(props.len(), 6);
5749        let prop = |i: usize| -> &hermes_ast::node::ObjectTypeProperty<'_> {
5750            let Node::ObjectTypeProperty(p) = props[i] else {
5751                panic!("expected ObjectTypeProperty, got {:?}", props[i].kind())
5752            };
5753            p
5754        };
5755
5756        // x: B
5757        let x = prop(0);
5758        assert_eq!(ident_bytes(&gc, x.key), b"x");
5759        assert!(!x.method.get() && !x.optional.get());
5760        assert!(!x.r#static.get() && !x.proto.get());
5761        assert!(x.variance.is_none());
5762        assert_eq!(gc.ctx().atom_table.bytes(x.kind.get()), b"init");
5763
5764        // y?: C
5765        let y = prop(1);
5766        assert!(y.optional.get());
5767
5768        // m(): D — a method; the value is a FunctionTypeAnnotation.
5769        let m = prop(2);
5770        assert!(m.method.get());
5771        assert_generic_named(&gc, as_fta(m.value).return_type, b"D");
5772        assert_eq!(gc.ctx().atom_table.bytes(m.kind.get()), b"init");
5773
5774        // get g(): E
5775        let g = prop(3);
5776        assert!(!g.method.get());
5777        assert_eq!(ident_bytes(&gc, g.key), b"g");
5778        assert_eq!(gc.ctx().atom_table.bytes(g.kind.get()), b"get");
5779
5780        // set s(v: F): void
5781        let s = prop(4);
5782        assert_eq!(gc.ctx().atom_table.bytes(s.kind.get()), b"set");
5783        assert_eq!(as_fta(s.value).params.iter().count(), 1);
5784
5785        // +ro: G
5786        let ro = prop(5);
5787        let variance = ro.variance.expect("has variance");
5788        let Node::Variance(v) = variance else {
5789            panic!("expected Variance, got {:?}", variance.kind())
5790        };
5791        assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
5792    }
5793
5794    /// Object types: indexers (with and without an id), mapped types with
5795    /// every optionality sigil, call properties, internal slots, spreads,
5796    /// exact `{| |}`, and explicit inexact `{ ... }`.
5797    #[test]
5798    fn flow_object_type_member_families() {
5799        use hermes_ast::context::Context;
5800        use hermes_ast::node::Node;
5801        let mut sm = hermes_support::manager::SourceErrorManager::new();
5802        let mut ctx = Context::new();
5803        ctx.set_parse_flow(true);
5804        let gc = ctx.lock();
5805
5806        let obj_of = |sm: &mut hermes_support::manager::SourceErrorManager,
5807                      src: &[u8]| {
5808            let ty = flow_alias_right(&gc, sm, src);
5809            let Node::ObjectTypeAnnotation(obj) = ty else {
5810                panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
5811            };
5812            obj
5813        };
5814
5815        // Indexer with an id: `[k: string]: V`.
5816        let obj = obj_of(&mut sm, b"type A = { [k: string]: V };");
5817        let idx = obj.indexers.iter().next().expect("one indexer");
5818        let Node::ObjectTypeIndexer(idx) = idx else {
5819            panic!("expected ObjectTypeIndexer, got {:?}", idx.kind())
5820        };
5821        assert_eq!(ident_bytes(&gc, idx.id.expect("has id")), b"k");
5822        assert!(matches!(idx.key, Node::StringTypeAnnotation(_)));
5823        assert!(!idx.r#static.get());
5824
5825        // Indexer without an id: `[K]: V`.
5826        let obj = obj_of(&mut sm, b"type B = { [K]: V };");
5827        let idx = obj.indexers.iter().next().expect("one indexer");
5828        let Node::ObjectTypeIndexer(idx) = idx else {
5829            panic!("expected ObjectTypeIndexer, got {:?}", idx.kind())
5830        };
5831        assert!(idx.id.is_none());
5832        assert_generic_named(&gc, idx.key, b"K");
5833
5834        // Mapped types: every optionality sigil (a null NodeString when no
5835        // sigil — matching the C++ nullptr → `"optional": null` dump).
5836        for (src, sigil) in [
5837            (b"type C = { [K in T]: V };".as_slice(), None),
5838            (b"type D = { [K in T]?: V };", Some(b"Optional".as_slice())),
5839            (b"type E = { [K in T]+?: V };", Some(b"PlusOptional")),
5840            (b"type F = { [K in T]-?: V };", Some(b"MinusOptional")),
5841        ] {
5842            let obj = obj_of(&mut sm, src);
5843            let prop = obj.properties.iter().next().expect("one property");
5844            let Node::ObjectTypeMappedTypeProperty(mapped) = prop else {
5845                panic!(
5846                    "expected ObjectTypeMappedTypeProperty, got {:?}",
5847                    prop.kind()
5848                )
5849            };
5850            let Node::TypeParameter(key_tparam) = mapped.key_tparam else {
5851                panic!("expected TypeParameter")
5852            };
5853            assert_eq!(
5854                gc.ctx().atom_table.bytes(key_tparam.name.get()),
5855                b"K"
5856            );
5857            assert_generic_named(&gc, mapped.source_type, b"T");
5858            assert_generic_named(&gc, mapped.prop_type, b"V");
5859            match sigil {
5860                None => assert_eq!(
5861                    mapped.optional.get(),
5862                    hermes_atom_table::INVALID_ATOM_BYTES,
5863                    "no sigil dumps as null"
5864                ),
5865                Some(s) => assert_eq!(
5866                    gc.ctx().atom_table.bytes(mapped.optional.get()),
5867                    s
5868                ),
5869            }
5870        }
5871
5872        // Mapped type with variance before the bracket: `+[K in T]: V`.
5873        let obj = obj_of(&mut sm, b"type G = { +[K in T]: V };");
5874        let prop = obj.properties.iter().next().expect("one property");
5875        let Node::ObjectTypeMappedTypeProperty(mapped) = prop else {
5876            panic!("expected ObjectTypeMappedTypeProperty")
5877        };
5878        assert!(mapped.variance.is_some());
5879
5880        // Call property + internal slot + spread.
5881        let obj =
5882            obj_of(&mut sm, b"type H = { (x: A): R, [[slot]]: T, ...S };");
5883        let call = obj.call_properties.iter().next().expect("one call");
5884        let Node::ObjectTypeCallProperty(call) = call else {
5885            panic!("expected ObjectTypeCallProperty, got {:?}", call.kind())
5886        };
5887        assert_eq!(as_fta(call.value).params.iter().count(), 1);
5888        let slot = obj.internal_slots.iter().next().expect("one slot");
5889        let Node::ObjectTypeInternalSlot(slot) = slot else {
5890            panic!("expected ObjectTypeInternalSlot, got {:?}", slot.kind())
5891        };
5892        assert_eq!(ident_bytes(&gc, slot.id), b"slot");
5893        assert!(!slot.method.get() && !slot.optional.get());
5894        let spread = obj.properties.iter().next().expect("one spread");
5895        let Node::ObjectTypeSpreadProperty(spread) = spread else {
5896            panic!("expected ObjectTypeSpreadProperty, got {:?}", spread.kind())
5897        };
5898        assert_generic_named(&gc, spread.argument, b"S");
5899
5900        // A method-typed internal slot: `[[m]](): R`.
5901        let obj = obj_of(&mut sm, b"type I = { [[m]](): R };");
5902        let slot = obj.internal_slots.iter().next().expect("one slot");
5903        let Node::ObjectTypeInternalSlot(slot) = slot else {
5904            panic!("expected ObjectTypeInternalSlot")
5905        };
5906        assert!(slot.method.get());
5907
5908        // Exact `{| |}` and explicit inexact `{ ... }`.
5909        let obj = obj_of(&mut sm, b"type J = {| a: T |};");
5910        assert!(obj.exact.get());
5911        let obj = obj_of(&mut sm, b"type K = { a: T, ... };");
5912        assert!(obj.inexact.get());
5913        let obj = obj_of(&mut sm, b"type L = { ... };");
5914        assert!(obj.inexact.get());
5915        assert!(obj.properties.is_empty());
5916    }
5917
5918    /// `static`/`proto` (and `readonly` before `:`) fall back to property
5919    /// and method names in an object type that disallows those modifiers.
5920    #[test]
5921    fn flow_object_type_modifier_name_fallbacks() {
5922        use hermes_ast::context::Context;
5923        use hermes_ast::node::Node;
5924        let mut sm = hermes_support::manager::SourceErrorManager::new();
5925        let mut ctx = Context::new();
5926        ctx.set_parse_flow(true);
5927        let gc = ctx.lock();
5928
5929        for (src, name, method) in [
5930            (b"type A = { static: T };".as_slice(), b"static".as_slice(), false),
5931            (b"type B = { proto: T };", b"proto", false),
5932            (b"type C = { static(): R };", b"static", true),
5933            (b"type D = { readonly: T };", b"readonly", false),
5934        ] {
5935            let ty = flow_alias_right(&gc, &mut sm, src);
5936            let Node::ObjectTypeAnnotation(obj) = ty else {
5937                panic!("expected ObjectTypeAnnotation, got {:?}", ty.kind())
5938            };
5939            let prop = obj.properties.iter().next().expect("one property");
5940            let Node::ObjectTypeProperty(prop) = prop else {
5941                panic!("expected ObjectTypeProperty, got {:?}", prop.kind())
5942            };
5943            assert_eq!(ident_bytes(&gc, prop.key), name);
5944            assert_eq!(prop.method.get(), method);
5945            assert!(!prop.r#static.get(), "the keyword was the name");
5946            assert!(!prop.proto.get(), "the keyword was the name");
5947        }
5948    }
5949
5950    /// Type-parameter declarations: const, sigil and keyword variance with
5951    /// the `in`/`out` name-vs-variance disambiguation, `:` vs `extends`
5952    /// bounds, and defaults.
5953    #[test]
5954    fn flow_type_param_declarations() {
5955        use hermes_ast::context::Context;
5956        use hermes_ast::node::Node;
5957        let mut sm = hermes_support::manager::SourceErrorManager::new();
5958        let mut ctx = Context::new();
5959        ctx.set_parse_flow(true);
5960        let gc = ctx.lock();
5961
5962        let params_of = |sm: &mut hermes_support::manager::SourceErrorManager,
5963                         src: &[u8]| {
5964            let stmt = parse_one_stmt(&gc, sm, src);
5965            let Node::TypeAlias(alias) = stmt else {
5966                panic!("expected TypeAlias, got {:?}", stmt.kind())
5967            };
5968            let tp = alias.type_parameters.expect("has type params");
5969            let Node::TypeParameterDeclaration(tpd) = tp else {
5970                panic!("expected TypeParameterDeclaration, got {:?}", tp.kind())
5971            };
5972            tpd.params.iter().collect::<Vec<_>>()
5973        };
5974        let tparam = |node: &'_ hermes_ast::node::Node<'_>| {
5975            let Node::TypeParameter(p) = node else {
5976                panic!("expected TypeParameter, got {:?}", node.kind())
5977            };
5978            let name = gc.ctx().atom_table.bytes(p.name.get()).to_vec();
5979            let variance = p.variance.map(|v| {
5980                let Node::Variance(v) = v else {
5981                    panic!("expected Variance, got {:?}", v.kind())
5982                };
5983                gc.ctx().atom_table.bytes(v.kind.get()).to_vec()
5984            });
5985            (name, variance)
5986        };
5987
5988        // Plain + trailing comma.
5989        let params = params_of(&mut sm, b"type A<T,> = T;");
5990        assert_eq!(params.len(), 1);
5991        assert_eq!(tparam(params[0]), (b"T".to_vec(), None));
5992
5993        // `const` modifier.
5994        let params = params_of(&mut sm, b"type B<const T> = T;");
5995        let Node::TypeParameter(p) = params[0] else { unreachable!() };
5996        assert!(p.r#const.get());
5997
5998        // Sigil variance.
5999        let params = params_of(&mut sm, b"type C<+T, -U> = [T, U];");
6000        assert_eq!(tparam(params[0]), (b"T".to_vec(), Some(b"plus".to_vec())));
6001        assert_eq!(tparam(params[1]), (b"U".to_vec(), Some(b"minus".to_vec())));
6002
6003        // `in T` / `out T`: the keyword is variance, `T` is the name.
6004        let params = params_of(&mut sm, b"type D<in T, out U> = [T, U];");
6005        assert_eq!(tparam(params[0]), (b"T".to_vec(), Some(b"in".to_vec())));
6006        assert_eq!(tparam(params[1]), (b"U".to_vec(), Some(b"out".to_vec())));
6007
6008        // `<in>` / `<out = X>`: the keyword is the NAME, no variance.
6009        let params = params_of(&mut sm, b"type E<in> = X;");
6010        assert_eq!(tparam(params[0]), (b"in".to_vec(), None));
6011        let params = params_of(&mut sm, b"type F<out = X> = out;");
6012        assert_eq!(tparam(params[0]), (b"out".to_vec(), None));
6013        let Node::TypeParameter(p) = params[0] else { unreachable!() };
6014        assert!(p.default.is_some(), "`= X` is the default");
6015
6016        // `:` bound (wrapped in TypeAnnotation) vs `extends` bound.
6017        let params = params_of(&mut sm, b"type G<T: number> = T;");
6018        let Node::TypeParameter(p) = params[0] else { unreachable!() };
6019        let bound = p.bound.expect("has bound");
6020        let Node::TypeAnnotation(bound) = bound else {
6021            panic!("expected TypeAnnotation, got {:?}", bound.kind())
6022        };
6023        assert!(matches!(
6024            bound.type_annotation,
6025            Node::NumberTypeAnnotation(_)
6026        ));
6027        assert!(!p.uses_extends_bound.get());
6028
6029        let params = params_of(&mut sm, b"type H<T extends U> = T;");
6030        let Node::TypeParameter(p) = params[0] else { unreachable!() };
6031        assert!(p.bound.is_some());
6032        assert!(p.uses_extends_bound.get());
6033
6034        // Default.
6035        let params = params_of(&mut sm, b"type I<T = string> = T;");
6036        let Node::TypeParameter(p) = params[0] else { unreachable!() };
6037        assert!(matches!(
6038            p.default.expect("has default"),
6039            Node::StringTypeAnnotation(_)
6040        ));
6041    }
6042
6043    /// Return-type predicates through function types: unprefixed `x is T`
6044    /// (null kind), `asserts x [is T]`, and `implies x is T`.
6045    #[test]
6046    fn flow_type_predicates() {
6047        use hermes_ast::context::Context;
6048        use hermes_ast::node::Node;
6049        let mut sm = hermes_support::manager::SourceErrorManager::new();
6050        let mut ctx = Context::new();
6051        ctx.set_parse_flow(true);
6052        let gc = ctx.lock();
6053
6054        let predicate_of = |sm: &mut hermes_support::manager::SourceErrorManager,
6055                            src: &[u8]| {
6056            let ty = flow_alias_right(&gc, sm, src);
6057            as_fta(ty).return_type
6058        };
6059
6060        // Unprefixed `x is number`: the kind is the null NodeString
6061        // (C++ passes nullptr; dumps as `"kind": null`).
6062        let ret = predicate_of(&mut sm, b"type A = (x: mixed) => x is number;");
6063        let Node::TypePredicate(p) = ret else {
6064            panic!("expected TypePredicate, got {:?}", ret.kind())
6065        };
6066        assert_eq!(ident_bytes(&gc, p.parameter_name), b"x");
6067        assert!(matches!(
6068            p.type_annotation.expect("has type"),
6069            Node::NumberTypeAnnotation(_)
6070        ));
6071        assert_eq!(p.kind.get(), hermes_atom_table::INVALID_ATOM_BYTES);
6072
6073        // `asserts x is T` and the type-less `asserts x`.
6074        let ret =
6075            predicate_of(&mut sm, b"type B = (x: mixed) => asserts x is T;");
6076        let Node::TypePredicate(p) = ret else {
6077            panic!("expected TypePredicate, got {:?}", ret.kind())
6078        };
6079        assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"asserts");
6080        assert!(p.type_annotation.is_some());
6081
6082        let ret = predicate_of(&mut sm, b"type C = (x: mixed) => asserts x;");
6083        let Node::TypePredicate(p) = ret else {
6084            panic!("expected TypePredicate, got {:?}", ret.kind())
6085        };
6086        assert!(p.type_annotation.is_none());
6087
6088        // A bare `asserts` return type is just a generic type.
6089        let ret = predicate_of(&mut sm, b"type D = (x: mixed) => asserts;");
6090        assert_generic_named(&gc, ret, b"asserts");
6091
6092        // `implies x is T`.
6093        let ret =
6094            predicate_of(&mut sm, b"type E = (x: mixed) => implies x is T;");
6095        let Node::TypePredicate(p) = ret else {
6096            panic!("expected TypePredicate, got {:?}", ret.kind())
6097        };
6098        assert_eq!(gc.ctx().atom_table.bytes(p.kind.get()), b"implies");
6099        assert!(p.type_annotation.is_some());
6100    }
6101
6102    /// `%checks` predicates: `parse_predicate_flow` is wired into function
6103    /// declarations in P5.4, so drive it directly — `%checks` only lexes as
6104    /// one identifier in Type grammar context.
6105    #[test]
6106    fn flow_checks_predicates() {
6107        use hermes_ast::context::Context;
6108        use hermes_ast::node::Node;
6109        use hermes_support::manager::SourceErrorManager;
6110
6111        let parse_predicate = |src: &[u8]| -> (&'static str, bool) {
6112            let mut sm = SourceErrorManager::new();
6113            let buf_id = sm.add_buffer_bytes("input", src);
6114            let mut ctx = Context::new();
6115            ctx.set_parse_flow(true);
6116            let gc = ctx.lock();
6117            let atoms = &gc.ctx().atom_table;
6118            let lexer = crate::lexer::JSLexer::new(
6119                buf_id,
6120                &mut sm,
6121                atoms,
6122                crate::lexer::GrammarContext::AllowRegExp,
6123            );
6124            let mut parser = JSParserImpl::new(&gc, lexer);
6125            // Skip the leading `x`, re-lexing in Type context so the
6126            // following `%checks` scans as a single identifier.
6127            parser.advance(crate::lexer::GrammarContext::Type);
6128            let pred =
6129                parser.parse_predicate_flow().expect("predicate parses");
6130            let kind = match pred {
6131                Node::DeclaredPredicate(d) => {
6132                    assert!(
6133                        matches!(d.value, Node::Identifier(_)),
6134                        "the checks expression is parsed as a JS expression"
6135                    );
6136                    "declared"
6137                }
6138                Node::InferredPredicate(_) => "inferred",
6139                other => panic!("unexpected predicate {:?}", other.kind()),
6140            };
6141            (kind, parser.error_count_pub() == 0)
6142        };
6143
6144        assert_eq!(parse_predicate(b"x %checks(y)"), ("declared", true));
6145        assert_eq!(parse_predicate(b"x %checks"), ("inferred", true));
6146    }
6147
6148    /// The P5.2 diagnostics keep the exact C++ texts.
6149    #[test]
6150    fn flow_p52_errors() {
6151        use hermes_ast::context::Context;
6152        use hermes_support::diag::{CollectingHandler, DiagKind};
6153        use hermes_support::manager::SourceErrorManager;
6154
6155        let assert_error = |src: &[u8], expected: &str| {
6156            let mut sm = SourceErrorManager::new();
6157            let mut ctx = Context::new();
6158            ctx.set_parse_flow(true);
6159            let gc = ctx.lock();
6160            let atoms = &gc.ctx().atom_table;
6161            let _ = parse_with_collector(&gc, &mut sm, atoms, src);
6162            let h = sm.handler_as::<CollectingHandler>().unwrap();
6163            assert!(
6164                h.messages()
6165                    .iter()
6166                    .any(|m| m.kind == DiagKind::Error && m.message == expected),
6167                "expected {:?}, got {:?}",
6168                expected,
6169                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
6170            );
6171        };
6172
6173        assert_error(
6174            b"type A = {| a: T, ... |};",
6175            "Explicit inexact syntax cannot appear inside an explicit exact object type",
6176        );
6177        assert_error(
6178            b"type A = { get x(a: B): T };",
6179            "Getter must have 0 parameters",
6180        );
6181        assert_error(
6182            b"type A = { set x(): void };",
6183            "Setter must have 1 parameter",
6184        );
6185        assert_error(
6186            b"type A = { get x(this: B): T };",
6187            "Accessors must not have 'this' annotations",
6188        );
6189        assert_error(
6190            b"type A = (this?: X) => Y;",
6191            "'this' constraint may not be optional",
6192        );
6193        assert_error(
6194            b"type A = (a: X, this: Y) => Z;",
6195            "'this' constraint must be the first parameter",
6196        );
6197        assert_error(
6198            b"type A = { +(): R };",
6199            "call property must not specify variance",
6200        );
6201        assert_error(
6202            b"type A = { +get x(): T };",
6203            "accessor property must not specify variance",
6204        );
6205        assert_error(b"type A = { proto x: T };", "invalid 'proto' modifier");
6206        assert_error(b"type A = { static x: T };", "invalid 'static' modifier");
6207        assert_error(b"type A = { +[[s]]: T };", "Unexpected variance sigil");
6208        // `implies<T>` parses as a generic WITH type args, so the following
6209        // identifier triggers the not-a-bare-identifier guard.
6210        assert_error(
6211            b"type A = (x) => implies<T> x is U;",
6212            "invalid return annotation. 'implies' type guard needs to be followed by identifier",
6213        );
6214        assert_error(
6215            b"type A = (x) => implies x;",
6216            "expecting 'is' after parameter of 'implies' type guard",
6217        );
6218    }
6219
6220    // P5.3: opaque type aliases, interface declarations/type annotations,
6221    // and class implements entries (js/flow/).
6222
6223    /// Helper: parse `src` with the caller's (e.g. Flow-enabled) context,
6224    /// expect zero errors, return the top-level statement at `idx` (for the
6225    /// strict-mode tests, where the directive prologue is statement 0).
6226    fn flow_parse_stmt_at<'gc>(
6227        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
6228        sm: &mut hermes_support::manager::SourceErrorManager,
6229        src: &[u8],
6230        idx: usize,
6231    ) -> &'gc hermes_ast::node::Node<'gc> {
6232        let buf_id = sm.add_buffer_bytes("input", src);
6233        let atoms = &gc.ctx().atom_table;
6234        let lexer = crate::lexer::JSLexer::new(
6235            buf_id,
6236            sm,
6237            atoms,
6238            crate::lexer::GrammarContext::AllowRegExp,
6239        );
6240        let mut parser = JSParserImpl::new(gc, lexer);
6241        let program = parser.parse().expect("parse succeeded");
6242        assert_eq!(parser.error_count_pub(), 0, "zero errors");
6243        if let hermes_ast::node::Node::Program(p) = program {
6244            return p.body.iter().nth(idx).expect("has enough statements");
6245        }
6246        panic!("expected Program");
6247    }
6248
6249    /// The `opaque type` alias shapes: plain, type params, the legacy
6250    /// `: Supertype`, and the `super`/`extends` bounds.
6251    #[test]
6252    fn flow_opaque_type_shapes() {
6253        use hermes_ast::context::Context;
6254        use hermes_ast::node::Node;
6255        use hermes_support::manager::SourceErrorManager;
6256
6257        // (src, has type params, lower bound, upper bound, supertype)
6258        let check = |src: &[u8],
6259                     has_tp: bool,
6260                     has_lower: bool,
6261                     has_upper: bool,
6262                     has_super: bool| {
6263            let mut sm = SourceErrorManager::new();
6264            let mut ctx = Context::new();
6265            ctx.set_parse_flow(true);
6266            let gc = ctx.lock();
6267            let stmt = parse_one_stmt(&gc, &mut sm, src);
6268            let Node::OpaqueType(o) = stmt else {
6269                panic!("expected OpaqueType, got {:?}", stmt.kind())
6270            };
6271            assert_eq!(o.type_parameters.is_some(), has_tp, "{src:?} tp");
6272            assert_eq!(o.lower_bound.is_some(), has_lower, "{src:?} lower");
6273            assert_eq!(o.upper_bound.is_some(), has_upper, "{src:?} upper");
6274            assert_eq!(o.supertype.is_some(), has_super, "{src:?} super");
6275        };
6276
6277        check(b"opaque type A = number;", false, false, false, false);
6278        check(b"opaque type B<T> = T;", true, false, false, false);
6279        check(b"opaque type C: number = 1;", false, false, false, true);
6280        check(b"opaque type D super X = Y;", false, true, false, false);
6281        check(b"opaque type E extends F = G;", false, false, true, false);
6282        check(
6283            b"opaque type H super X extends F = G;",
6284            false,
6285            true,
6286            true,
6287            false,
6288        );
6289
6290        // The node shape of the legacy-supertype form.
6291        let mut sm = SourceErrorManager::new();
6292        let mut ctx = Context::new();
6293        ctx.set_parse_flow(true);
6294        let gc = ctx.lock();
6295        let stmt = parse_one_stmt(&gc, &mut sm, b"opaque type C: number = 1;");
6296        let Node::OpaqueType(o) = stmt else {
6297            panic!("expected OpaqueType, got {:?}", stmt.kind())
6298        };
6299        assert_eq!(ident_bytes(&gc, o.id), b"C");
6300        assert!(
6301            matches!(o.supertype, Some(Node::NumberTypeAnnotation(_))),
6302            "supertype is NumberTypeAnnotation"
6303        );
6304        assert!(
6305            matches!(o.impltype, Node::NumberLiteralTypeAnnotation(_)),
6306            "impltype is NumberLiteralTypeAnnotation, got {:?}",
6307            o.impltype.kind()
6308        );
6309    }
6310
6311    /// Interface declarations: id, type params, the `extends` list (with the
6312    /// GenericTypeAnnotation → InterfaceExtends unwrapping), and the body.
6313    #[test]
6314    fn flow_interface_declaration() {
6315        use hermes_ast::context::Context;
6316        use hermes_ast::node::Node;
6317        use hermes_support::manager::SourceErrorManager;
6318
6319        let mut sm = SourceErrorManager::new();
6320        let mut ctx = Context::new();
6321        ctx.set_parse_flow(true);
6322        let gc = ctx.lock();
6323
6324        // Plain interface with one property.
6325        let stmt = parse_one_stmt(&gc, &mut sm, b"interface I { x: number }");
6326        let Node::InterfaceDeclaration(decl) = stmt else {
6327            panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
6328        };
6329        assert_eq!(ident_bytes(&gc, decl.id), b"I");
6330        assert!(decl.type_parameters.is_none(), "no type params");
6331        assert!(decl.extends.is_empty(), "no extends");
6332        let Node::ObjectTypeAnnotation(body) = decl.body else {
6333            panic!("expected ObjectTypeAnnotation body")
6334        };
6335        assert_eq!(body.properties.iter().count(), 1, "one property");
6336
6337        // Type params + a two-entry extends list; the second entry keeps the
6338        // generic's type arguments.
6339        let stmt = parse_one_stmt(
6340            &gc,
6341            &mut sm,
6342            b"interface J<T> extends K, L<T> { m(): void }",
6343        );
6344        let Node::InterfaceDeclaration(decl) = stmt else {
6345            panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
6346        };
6347        assert_eq!(ident_bytes(&gc, decl.id), b"J");
6348        assert!(decl.type_parameters.is_some(), "has type params");
6349        let extends: Vec<_> = decl.extends.iter().collect();
6350        assert_eq!(extends.len(), 2, "two extends entries");
6351        let Node::InterfaceExtends(e0) = extends[0] else {
6352            panic!("expected InterfaceExtends, got {:?}", extends[0].kind())
6353        };
6354        assert_eq!(ident_bytes(&gc, e0.id), b"K");
6355        assert!(e0.type_parameters.is_none(), "K has no type args");
6356        let Node::InterfaceExtends(e1) = extends[1] else {
6357            panic!("expected InterfaceExtends, got {:?}", extends[1].kind())
6358        };
6359        assert_eq!(ident_bytes(&gc, e1.id), b"L");
6360        assert!(e1.type_parameters.is_some(), "L<T> keeps its type args");
6361
6362        // Empty body.
6363        let stmt = parse_one_stmt(&gc, &mut sm, b"interface E {}");
6364        let Node::InterfaceDeclaration(decl) = stmt else {
6365            panic!("expected InterfaceDeclaration, got {:?}", stmt.kind())
6366        };
6367        let Node::ObjectTypeAnnotation(body) = decl.body else {
6368            panic!("expected ObjectTypeAnnotation body")
6369        };
6370        assert!(body.properties.is_empty(), "empty body");
6371    }
6372
6373    /// `interface { ... }` as a TYPE annotation, in both spellings:
6374    /// loose mode lexes `interface` as a plain identifier (the
6375    /// NamedType::Interface arm); strict mode lexes it as rw_interface (the
6376    /// reserved-word arm). Both build InterfaceTypeAnnotation.
6377    #[test]
6378    fn flow_interface_type_annotation() {
6379        use hermes_ast::context::Context;
6380        use hermes_ast::node::Node;
6381        use hermes_support::manager::SourceErrorManager;
6382
6383        let mut sm = SourceErrorManager::new();
6384        let mut ctx = Context::new();
6385        ctx.set_parse_flow(true);
6386        let gc = ctx.lock();
6387
6388        // Loose mode: the identifier arm.
6389        let right =
6390            flow_alias_right(&gc, &mut sm, b"type A = interface { x: number };");
6391        let Node::InterfaceTypeAnnotation(ita) = right else {
6392            panic!("expected InterfaceTypeAnnotation, got {:?}", right.kind())
6393        };
6394        assert!(ita.extends.is_empty(), "no extends");
6395        assert!(
6396            matches!(ita.body, Some(Node::ObjectTypeAnnotation(_))),
6397            "body is ObjectTypeAnnotation"
6398        );
6399
6400        // An interface type with an extends clause.
6401        let right = flow_alias_right(
6402            &gc,
6403            &mut sm,
6404            b"type B = interface extends I { y: T };",
6405        );
6406        let Node::InterfaceTypeAnnotation(ita) = right else {
6407            panic!("expected InterfaceTypeAnnotation, got {:?}", right.kind())
6408        };
6409        let extends: Vec<_> = ita.extends.iter().collect();
6410        assert_eq!(extends.len(), 1, "one extends entry");
6411        assert!(matches!(extends[0], Node::InterfaceExtends(_)));
6412
6413        // Strict mode: the rw_interface arm (type position).
6414        let stmt = flow_parse_stmt_at(
6415            &gc,
6416            &mut sm,
6417            b"'use strict'; type C = interface { x: number };",
6418            1,
6419        );
6420        let Node::TypeAlias(alias) = stmt else {
6421            panic!("expected TypeAlias, got {:?}", stmt.kind())
6422        };
6423        assert!(
6424            matches!(alias.right, Node::InterfaceTypeAnnotation(_)),
6425            "rw_interface arm builds InterfaceTypeAnnotation, got {:?}",
6426            alias.right.kind()
6427        );
6428
6429        // Strict mode: the rw_interface arm (declaration position).
6430        let stmt = flow_parse_stmt_at(
6431            &gc,
6432            &mut sm,
6433            b"'use strict'; interface S { x: number }",
6434            1,
6435        );
6436        assert!(
6437            matches!(stmt, Node::InterfaceDeclaration(_)),
6438            "rw_interface declaration parses, got {:?}",
6439            stmt.kind()
6440        );
6441    }
6442
6443    /// `parse_class_implements_flow` (direct call — the class-heritage
6444    /// integration lands in P5.4): `I` and `I<T>`.
6445    #[test]
6446    fn flow_class_implements() {
6447        use hermes_ast::context::Context;
6448        use hermes_ast::node::Node;
6449        use hermes_support::manager::SourceErrorManager;
6450
6451        let parse_impl = |src: &[u8], expect_args: bool| {
6452            let mut sm = SourceErrorManager::new();
6453            let buf_id = sm.add_buffer_bytes("input", src);
6454            let mut ctx = Context::new();
6455            ctx.set_parse_flow(true);
6456            let gc = ctx.lock();
6457            let atoms = &gc.ctx().atom_table;
6458            let lexer = crate::lexer::JSLexer::new(
6459                buf_id,
6460                &mut sm,
6461                atoms,
6462                crate::lexer::GrammarContext::AllowRegExp,
6463            );
6464            let mut parser = JSParserImpl::new(&gc, lexer);
6465            let node = parser
6466                .parse_class_implements_flow()
6467                .expect("class implements parses");
6468            let Node::ClassImplements(ci) = node else {
6469                panic!("expected ClassImplements, got {:?}", node.kind())
6470            };
6471            assert_eq!(ident_bytes(&gc, ci.id), b"I");
6472            assert_eq!(
6473                ci.type_parameters.is_some(),
6474                expect_args,
6475                "{src:?} type args"
6476            );
6477            assert_eq!(parser.error_count_pub(), 0, "zero errors");
6478        };
6479
6480        parse_impl(b"I", false);
6481        parse_impl(b"I<T>", true);
6482    }
6483
6484    /// The P5.3 diagnostics keep the exact C++ texts.
6485    #[test]
6486    fn flow_p53_errors() {
6487        use hermes_ast::context::Context;
6488        use hermes_support::diag::{CollectingHandler, DiagKind};
6489        use hermes_support::manager::SourceErrorManager;
6490
6491        let assert_error = |src: &[u8], expected: &str| {
6492            let mut sm = SourceErrorManager::new();
6493            let mut ctx = Context::new();
6494            ctx.set_parse_flow(true);
6495            let gc = ctx.lock();
6496            let atoms = &gc.ctx().atom_table;
6497            let _ = parse_with_collector(&gc, &mut sm, atoms, src);
6498            let h = sm.handler_as::<CollectingHandler>().unwrap();
6499            assert!(
6500                h.messages()
6501                    .iter()
6502                    .any(|m| m.kind == DiagKind::Error && m.message == expected),
6503                "expected {:?}, got {:?}",
6504                expected,
6505                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
6506            );
6507        };
6508
6509        // Interface bodies pass AllowSpreadProperty::No, finally making this
6510        // P5.2 diagnostic reachable.
6511        assert_error(
6512            b"interface I { ...T }",
6513            "Spreading a type is only allowed inside an object type",
6514        );
6515        // An opaque alias requires `= T` (only DeclareOpaque may omit it).
6516        assert_error(b"opaque type X;", "'=' expected in type alias");
6517        // `opaque` must be followed by `type`.
6518        assert_error(
6519            b"opaque interface I {}",
6520            "invalid token in opaque type declaration",
6521        );
6522    }
6523
6524    // P5.4: Flow non-ambiguous integration — the type grammar hung off the
6525    // core productions (functions, params, bindings, classes, object-literal
6526    // methods).
6527
6528    /// Function signature: type params, annotated params, return type.
6529    #[test]
6530    fn flow_function_signature() {
6531        use hermes_ast::context::Context;
6532        use hermes_ast::node::Node;
6533        use hermes_support::manager::SourceErrorManager;
6534
6535        let mut sm = SourceErrorManager::new();
6536        let mut ctx = Context::new();
6537        ctx.set_parse_flow(true);
6538        let gc = ctx.lock();
6539        let stmt = parse_one_stmt(
6540            &gc,
6541            &mut sm,
6542            b"function f<T>(x: T): T { return x; }",
6543        );
6544        let Node::FunctionDeclaration(f) = stmt else {
6545            panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
6546        };
6547        assert!(f.type_parameters.is_some(), "type params");
6548        assert!(f.return_type.is_some(), "return type");
6549        assert!(f.predicate.is_none(), "no predicate");
6550        let param = f.params.iter().next().expect("one param");
6551        let Node::Identifier(p) = param else {
6552            panic!("expected Identifier param, got {:?}", param.kind())
6553        };
6554        assert!(p.type_annotation.is_some(), "param annotation");
6555        assert!(!p.optional.get(), "param not optional");
6556    }
6557
6558    /// `%checks` predicates: inferred (after a return type) and declared
6559    /// (directly after the colon, with no return type).
6560    #[test]
6561    fn flow_function_predicates() {
6562        use hermes_ast::context::Context;
6563        use hermes_ast::node::Node;
6564        use hermes_support::manager::SourceErrorManager;
6565
6566        // Return type + inferred predicate.
6567        {
6568            let mut sm = SourceErrorManager::new();
6569            let mut ctx = Context::new();
6570            ctx.set_parse_flow(true);
6571            let gc = ctx.lock();
6572            let stmt = parse_one_stmt(
6573                &gc,
6574                &mut sm,
6575                b"function p(x: mixed): boolean %checks { return !!x; }",
6576            );
6577            let Node::FunctionDeclaration(f) = stmt else {
6578                panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
6579            };
6580            assert!(f.return_type.is_some(), "return type");
6581            assert!(
6582                matches!(f.predicate, Some(Node::InferredPredicate(_))),
6583                "inferred predicate"
6584            );
6585        }
6586
6587        // Declared predicate with NO return type (`): %checks(expr)`).
6588        {
6589            let mut sm = SourceErrorManager::new();
6590            let mut ctx = Context::new();
6591            ctx.set_parse_flow(true);
6592            let gc = ctx.lock();
6593            let stmt = parse_one_stmt(
6594                &gc,
6595                &mut sm,
6596                b"function q(x: mixed): %checks (x === 1) {}",
6597            );
6598            let Node::FunctionDeclaration(f) = stmt else {
6599                panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
6600            };
6601            assert!(f.return_type.is_none(), "no return type");
6602            assert!(
6603                matches!(f.predicate, Some(Node::DeclaredPredicate(_))),
6604                "declared predicate"
6605            );
6606        }
6607    }
6608
6609    /// A leading `this` parameter is pushed as the FIRST formal parameter,
6610    /// with its type annotation and the following comma consumed.
6611    #[test]
6612    fn flow_this_param() {
6613        use hermes_ast::context::Context;
6614        use hermes_ast::node::Node;
6615        use hermes_support::manager::SourceErrorManager;
6616
6617        let mut sm = SourceErrorManager::new();
6618        let mut ctx = Context::new();
6619        ctx.set_parse_flow(true);
6620        let gc = ctx.lock();
6621        let stmt = parse_one_stmt(
6622            &gc,
6623            &mut sm,
6624            b"function g(this: Object, a: number): void {}",
6625        );
6626        let Node::FunctionDeclaration(f) = stmt else {
6627            panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
6628        };
6629        let params: Vec<_> = f.params.iter().collect();
6630        assert_eq!(params.len(), 2, "two params");
6631        let Node::Identifier(this_param) = params[0] else {
6632            panic!("expected Identifier, got {:?}", params[0].kind())
6633        };
6634        assert_eq!(
6635            gc.ctx().atom_table.bytes(this_param.name.get()),
6636            b"this",
6637            "first param is 'this'"
6638        );
6639        assert!(this_param.type_annotation.is_some(), "'this' annotation");
6640        assert!(!this_param.optional.get());
6641        let Node::Identifier(a_param) = params[1] else {
6642            panic!("expected Identifier, got {:?}", params[1].kind())
6643        };
6644        assert_eq!(gc.ctx().atom_table.bytes(a_param.name.get()), b"a");
6645    }
6646
6647    /// Binding annotations: the `?` optional marker and `:` type on binding
6648    /// identifiers, and `:` types on array/object binding patterns.
6649    #[test]
6650    fn flow_binding_annotations() {
6651        use hermes_ast::context::Context;
6652        use hermes_ast::node::Node;
6653        use hermes_support::manager::SourceErrorManager;
6654
6655        /// The single declarator's id of the variable declaration in `src`.
6656        fn decl_id<'gc>(
6657            gc: &'gc hermes_ast::context::GCLock<'_, '_>,
6658            src: &[u8],
6659        ) -> &'gc Node<'gc> {
6660            let mut sm = SourceErrorManager::new();
6661            let stmt = parse_one_stmt(gc, &mut sm, src);
6662            let Node::VariableDeclaration(d) = stmt else {
6663                panic!("expected VariableDeclaration, got {:?}", stmt.kind())
6664            };
6665            let Node::VariableDeclarator(declarator) =
6666                d.declarations.iter().next().expect("one declarator")
6667            else {
6668                panic!("expected VariableDeclarator")
6669            };
6670            declarator.id
6671        }
6672
6673        let mut ctx = Context::new();
6674        ctx.set_parse_flow(true);
6675        let gc = ctx.lock();
6676
6677        // `?` + `:` on a binding identifier.
6678        let id = decl_id(&gc, b"var a?: number;");
6679        let Node::Identifier(id) = id else {
6680            panic!("expected Identifier, got {:?}", id.kind())
6681        };
6682        assert!(id.optional.get(), "optional");
6683        assert!(id.type_annotation.is_some(), "id annotation");
6684
6685        // `:` on an array binding pattern.
6686        let pat = decl_id(&gc, b"var [x, y]: T = c;");
6687        let Node::ArrayPattern(pat) = pat else {
6688            panic!("expected ArrayPattern, got {:?}", pat.kind())
6689        };
6690        assert!(pat.type_annotation.is_some(), "array pattern annotation");
6691
6692        // `:` on an object binding pattern.
6693        let pat = decl_id(&gc, b"var {x}: T = c;");
6694        let Node::ObjectPattern(pat) = pat else {
6695            panic!("expected ObjectPattern, got {:?}", pat.kind())
6696        };
6697        assert!(pat.type_annotation.is_some(), "object pattern annotation");
6698
6699        // Optional parameter `a?: T` in a formal parameter list.
6700        let mut sm = SourceErrorManager::new();
6701        let stmt = parse_one_stmt(&gc, &mut sm, b"function fd(a?: T) {}");
6702        let Node::FunctionDeclaration(f) = stmt else {
6703            panic!("expected FunctionDeclaration, got {:?}", stmt.kind())
6704        };
6705        let param = f.params.iter().next().expect("one param");
6706        let Node::Identifier(p) = param else {
6707            panic!("expected Identifier param, got {:?}", param.kind())
6708        };
6709        assert!(p.optional.get(), "optional param");
6710        assert!(p.type_annotation.is_some(), "optional param annotation");
6711    }
6712
6713    /// Class integration: class/method type params, super-class type args,
6714    /// the implements clause, field annotations + variance, method/getter
6715    /// return types, and private-field annotations.
6716    #[test]
6717    fn flow_class_integration() {
6718        use hermes_ast::context::Context;
6719        use hermes_ast::node::Node;
6720        use hermes_support::manager::SourceErrorManager;
6721
6722        let mut sm = SourceErrorManager::new();
6723        let mut ctx = Context::new();
6724        ctx.set_parse_flow(true);
6725        let gc = ctx.lock();
6726        let stmt = parse_one_stmt(
6727            &gc,
6728            &mut sm,
6729            b"class C<T> extends B<T> implements I, J<T> {\n\
6730              \x20 x: number;\n\
6731              \x20 +ro: T;\n\
6732              \x20 readonly r: V;\n\
6733              \x20 #p: T;\n\
6734              \x20 static: number;\n\
6735              \x20 m<U>(a: U): U { return a; }\n\
6736              \x20 get g(): T { return this.x; }\n\
6737              }",
6738        );
6739        let Node::ClassDeclaration(c) = stmt else {
6740            panic!("expected ClassDeclaration, got {:?}", stmt.kind())
6741        };
6742        assert!(c.type_parameters.is_some(), "class type params");
6743        assert!(c.super_class.is_some(), "super class");
6744        assert!(c.super_type_arguments.is_some(), "super type args");
6745
6746        // implements I, J<T>
6747        let impls: Vec<_> = c.implements.iter().collect();
6748        assert_eq!(impls.len(), 2, "two implements entries");
6749        let Node::ClassImplements(i0) = impls[0] else {
6750            panic!("expected ClassImplements, got {:?}", impls[0].kind())
6751        };
6752        assert!(i0.type_parameters.is_none(), "I has no type args");
6753        let Node::ClassImplements(i1) = impls[1] else {
6754            panic!("expected ClassImplements, got {:?}", impls[1].kind())
6755        };
6756        assert!(i1.type_parameters.is_some(), "J<T> has type args");
6757
6758        let Node::ClassBody(body) = c.body else {
6759            panic!("expected ClassBody")
6760        };
6761        let elems: Vec<_> = body.body.iter().collect();
6762        assert_eq!(elems.len(), 7, "seven class elements");
6763
6764        // x: number;
6765        let Node::ClassProperty(x) = elems[0] else {
6766            panic!("expected ClassProperty, got {:?}", elems[0].kind())
6767        };
6768        assert!(x.type_annotation.is_some(), "x annotation");
6769        assert!(x.variance.is_none(), "x has no variance");
6770
6771        // +ro: T;
6772        let Node::ClassProperty(ro) = elems[1] else {
6773            panic!("expected ClassProperty, got {:?}", elems[1].kind())
6774        };
6775        let Some(Node::Variance(v)) = ro.variance else {
6776            panic!("expected Variance on +ro")
6777        };
6778        assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"plus");
6779
6780        // readonly r: V; (contextual-keyword variance)
6781        let Node::ClassProperty(r) = elems[2] else {
6782            panic!("expected ClassProperty, got {:?}", elems[2].kind())
6783        };
6784        let Some(Node::Variance(v)) = r.variance else {
6785            panic!("expected Variance on readonly r")
6786        };
6787        assert_eq!(gc.ctx().atom_table.bytes(v.kind.get()), b"readonly");
6788
6789        // #p: T;
6790        let Node::ClassPrivateProperty(p) = elems[3] else {
6791            panic!("expected ClassPrivateProperty, got {:?}", elems[3].kind())
6792        };
6793        assert!(p.type_annotation.is_some(), "#p annotation");
6794
6795        // static: number; — `static` is the property NAME here.
6796        let Node::ClassProperty(s) = elems[4] else {
6797            panic!("expected ClassProperty, got {:?}", elems[4].kind())
6798        };
6799        let Node::Identifier(s_key) = s.key else {
6800            panic!("expected Identifier key")
6801        };
6802        assert_eq!(gc.ctx().atom_table.bytes(s_key.name.get()), b"static");
6803        assert!(!s.r#static.get(), "'static' is the name, not a modifier");
6804        assert!(s.type_annotation.is_some(), "static-field annotation");
6805
6806        // m<U>(a: U): U {}
6807        let Node::MethodDefinition(m) = elems[5] else {
6808            panic!("expected MethodDefinition, got {:?}", elems[5].kind())
6809        };
6810        let Node::FunctionExpression(mf) = m.value else {
6811            panic!("expected FunctionExpression")
6812        };
6813        assert!(mf.type_parameters.is_some(), "method type params");
6814        assert!(mf.return_type.is_some(), "method return type");
6815
6816        // get g(): T {}
6817        let Node::MethodDefinition(getter) = elems[6] else {
6818            panic!("expected MethodDefinition, got {:?}", elems[6].kind())
6819        };
6820        assert_eq!(gc.ctx().atom_table.bytes(getter.kind.get()), b"get");
6821        let Node::FunctionExpression(gf) = getter.value else {
6822            panic!("expected FunctionExpression")
6823        };
6824        assert!(gf.return_type.is_some(), "getter return type");
6825    }
6826
6827    /// An anonymous class expression: with Flow, `<`/`implements` after
6828    /// `class` means there is no class name.
6829    #[test]
6830    fn flow_class_expression_heritage() {
6831        use hermes_ast::context::Context;
6832        use hermes_ast::node::Node;
6833        use hermes_support::manager::SourceErrorManager;
6834
6835        let mut sm = SourceErrorManager::new();
6836        let mut ctx = Context::new();
6837        ctx.set_parse_flow(true);
6838        let gc = ctx.lock();
6839        let atoms = &gc.ctx().atom_table;
6840        let expr = parse_expr_from(
6841            &gc,
6842            &mut sm,
6843            atoms,
6844            b"(class <T> implements K { y: T; });",
6845        );
6846        let Node::ClassExpression(c) = expr else {
6847            panic!("expected ClassExpression, got {:?}", expr.kind())
6848        };
6849        assert!(c.id.is_none(), "anonymous");
6850        assert!(c.type_parameters.is_some(), "type params");
6851        assert_eq!(c.implements.iter().count(), 1, "one implements entry");
6852    }
6853
6854    /// Object-literal methods: type params and return types, plus
6855    /// `get`/`set` used as method names (detected via `<`).
6856    #[test]
6857    fn flow_object_literal_methods() {
6858        use hermes_ast::context::Context;
6859        use hermes_ast::node::Node;
6860        use hermes_support::manager::SourceErrorManager;
6861
6862        let mut sm = SourceErrorManager::new();
6863        let mut ctx = Context::new();
6864        ctx.set_parse_flow(true);
6865        let gc = ctx.lock();
6866        let atoms = &gc.ctx().atom_table;
6867        let expr = parse_expr_from(
6868            &gc,
6869            &mut sm,
6870            atoms,
6871            b"({ m<T>(x: T): T { return x; },\n\
6872              \x20  get x(): number { return 1; },\n\
6873              \x20  set y(v: number): void {},\n\
6874              \x20  get<T>(x) { return x; } });",
6875        );
6876        let Node::ObjectExpression(obj) = expr else {
6877            panic!("expected ObjectExpression, got {:?}", expr.kind())
6878        };
6879        let props: Vec<_> = obj.properties.iter().collect();
6880        assert_eq!(props.len(), 4, "four properties");
6881
6882        // m<T>(x: T): T {}
6883        let Node::Property(m) = props[0] else {
6884            panic!("expected Property")
6885        };
6886        assert!(m.method.get(), "m is a method");
6887        let Node::FunctionExpression(mf) = m.value else {
6888            panic!("expected FunctionExpression")
6889        };
6890        assert!(mf.type_parameters.is_some(), "method type params");
6891        assert!(mf.return_type.is_some(), "method return type");
6892
6893        // get x(): number {}
6894        let Node::Property(g) = props[1] else {
6895            panic!("expected Property")
6896        };
6897        assert_eq!(gc.ctx().atom_table.bytes(g.kind.get()), b"get");
6898        let Node::FunctionExpression(gf) = g.value else {
6899            panic!("expected FunctionExpression")
6900        };
6901        assert!(gf.return_type.is_some(), "getter return type");
6902
6903        // set y(v: number): void {}
6904        let Node::Property(s) = props[2] else {
6905            panic!("expected Property")
6906        };
6907        assert_eq!(gc.ctx().atom_table.bytes(s.kind.get()), b"set");
6908        let Node::FunctionExpression(sf) = s.value else {
6909            panic!("expected FunctionExpression")
6910        };
6911        assert!(sf.return_type.is_some(), "setter return type");
6912
6913        // get<T>(x) {} — a method NAMED "get" (the `<` routes to a method).
6914        let Node::Property(gm) = props[3] else {
6915            panic!("expected Property")
6916        };
6917        assert!(gm.method.get(), "get<T> is a method");
6918        let Node::Identifier(gm_key) = gm.key else {
6919            panic!("expected Identifier key")
6920        };
6921        assert_eq!(gc.ctx().atom_table.bytes(gm_key.name.get()), b"get");
6922        let Node::FunctionExpression(gmf) = gm.value else {
6923            panic!("expected FunctionExpression")
6924        };
6925        assert!(gmf.type_parameters.is_some(), "get<T> type params");
6926    }
6927
6928    /// The P5.4 class-element diagnostics keep the exact C++ texts.
6929    #[test]
6930    fn flow_p54_errors() {
6931        use hermes_ast::context::Context;
6932        use hermes_support::diag::{CollectingHandler, DiagKind};
6933        use hermes_support::manager::SourceErrorManager;
6934
6935        let assert_error = |src: &[u8], expected: &str| {
6936            let mut sm = SourceErrorManager::new();
6937            let mut ctx = Context::new();
6938            ctx.set_parse_flow(true);
6939            let gc = ctx.lock();
6940            let atoms = &gc.ctx().atom_table;
6941            let _ = parse_with_collector(&gc, &mut sm, atoms, src);
6942            let h = sm.handler_as::<CollectingHandler>().unwrap();
6943            assert!(
6944                h.messages()
6945                    .iter()
6946                    .any(|m| m.kind == DiagKind::Error && m.message == expected),
6947                "expected {:?}, got {:?}",
6948                expected,
6949                h.messages().iter().map(|m| &m.message).collect::<Vec<_>>()
6950            );
6951        };
6952
6953        // C++ JSParserImpl.cpp:5619-5626.
6954        assert_error(
6955            b"class C { get x<T>() { return 1; } }",
6956            "accessor method may not have type parameters",
6957        );
6958        // C++ JSParserImpl.cpp:5670-5672 (variance is only valid on fields).
6959        assert_error(b"class C { +m() {} }", "Unexpected variance sigil");
6960    }
6961
6962    // -----------------------------------------------------------------------
6963    // P6.3 — Flow component/hook syntax + hook type annotation.
6964    // -----------------------------------------------------------------------
6965
6966    /// Parse `src` with both `parse_flow` and `parse_flow_component_syntax`
6967    /// enabled, expect zero errors, and return the `idx`th top-level
6968    /// statement.
6969    fn comp_parse_stmt_at<'gc>(
6970        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
6971        sm: &mut hermes_support::manager::SourceErrorManager,
6972        src: &[u8],
6973        idx: usize,
6974    ) -> &'gc hermes_ast::node::Node<'gc> {
6975        let buf_id = sm.add_buffer_bytes("input", src);
6976        let atoms = &gc.ctx().atom_table;
6977        let lexer = crate::lexer::JSLexer::new(
6978            buf_id,
6979            sm,
6980            atoms,
6981            crate::lexer::GrammarContext::AllowRegExp,
6982        );
6983        let mut parser = JSParserImpl::new(gc, lexer);
6984        let program = parser.parse().expect("parse succeeded");
6985        assert_eq!(parser.error_count_pub(), 0, "zero errors for {src:?}");
6986        if let hermes_ast::node::Node::Program(p) = program {
6987            return p.body.iter().nth(idx).expect("has enough statements");
6988        }
6989        panic!("expected Program");
6990    }
6991
6992    /// Build a component-syntax-enabled `Context` (Flow + component syntax).
6993    fn comp_ctx() -> hermes_ast::context::Context<'static> {
6994        let mut ctx = hermes_ast::context::Context::new();
6995        ctx.set_parse_flow(true);
6996        ctx.set_parse_flow_component_syntax(true);
6997        ctx
6998    }
6999
7000    /// `component Foo() {}` — basic component declaration.
7001    #[test]
7002    fn flow_component_basic() {
7003        use hermes_ast::node::Node;
7004        let mut sm = hermes_support::manager::SourceErrorManager::new();
7005        let mut ctx = comp_ctx();
7006        let gc = ctx.lock();
7007        let stmt = comp_parse_stmt_at(&gc, &mut sm, b"component Foo() {}", 0);
7008        let Node::ComponentDeclaration(c) = stmt else {
7009            panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
7010        };
7011        assert_eq!(ident_bytes(&gc, c.id), b"Foo");
7012        assert_eq!(c.params.iter().count(), 0);
7013        assert!(c.type_parameters.is_none());
7014        assert!(c.renders_type.is_none());
7015        assert!(!c.r#async.get());
7016    }
7017
7018    /// The three component-parameter shapes: string-literal name with `as`
7019    /// local, ident name with `as` local, and the shorthand
7020    /// `ident?: T = init` (plus a rest element).
7021    #[test]
7022    fn flow_component_parameters() {
7023        use hermes_ast::node::Node;
7024        let mut sm = hermes_support::manager::SourceErrorManager::new();
7025        let mut ctx = comp_ctx();
7026        let gc = ctx.lock();
7027        let stmt = comp_parse_stmt_at(
7028            &gc,
7029            &mut sm,
7030            b"component Foo(\"data-id\" as id, name?: string, x: number = 5, ...rest: Props) {}",
7031            0,
7032        );
7033        let Node::ComponentDeclaration(c) = stmt else {
7034            panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
7035        };
7036        let params: Vec<_> = c.params.iter().collect();
7037        assert_eq!(params.len(), 4);
7038
7039        // "data-id" as id — string-literal name, not shorthand.
7040        let Node::ComponentParameter(p0) = params[0] else {
7041            panic!("expected ComponentParameter, got {:?}", params[0].kind())
7042        };
7043        assert!(matches!(p0.name, Node::StringLiteral(_)));
7044        assert!(!p0.shorthand.get());
7045
7046        // name?: string — shorthand, optional, no `as`.
7047        let Node::ComponentParameter(p1) = params[1] else {
7048            panic!("expected ComponentParameter, got {:?}", params[1].kind())
7049        };
7050        assert!(p1.shorthand.get());
7051        let Node::Identifier(local1) = p1.local else {
7052            panic!("expected Identifier local, got {:?}", p1.local.kind())
7053        };
7054        assert!(local1.optional.get());
7055        assert!(local1.type_annotation.is_some());
7056
7057        // x: number = 5 — shorthand with a default (AssignmentPattern local).
7058        let Node::ComponentParameter(p2) = params[2] else {
7059            panic!("expected ComponentParameter, got {:?}", params[2].kind())
7060        };
7061        assert!(p2.shorthand.get());
7062        assert!(matches!(p2.local, Node::AssignmentPattern(_)));
7063
7064        // ...rest: Props — a rest element (a RestElement, not a
7065        // ComponentParameter).
7066        assert!(matches!(params[3], Node::RestElement(_)));
7067    }
7068
7069    /// `renders` / `renders?` / `renders*` operators on component
7070    /// declarations, with the operator label captured on the `TypeOperator`.
7071    #[test]
7072    fn flow_component_renders_operators() {
7073        use hermes_ast::node::Node;
7074        let check = |src: &[u8], op: &[u8]| {
7075            let mut sm = hermes_support::manager::SourceErrorManager::new();
7076            let mut ctx = comp_ctx();
7077            let gc = ctx.lock();
7078            let stmt = comp_parse_stmt_at(&gc, &mut sm, src, 0);
7079            let Node::ComponentDeclaration(c) = stmt else {
7080                panic!("expected ComponentDeclaration, got {:?}", stmt.kind())
7081            };
7082            let renders = c.renders_type.expect("has renders type");
7083            let Node::TypeOperator(t) = renders else {
7084                panic!("expected TypeOperator, got {:?}", renders.kind())
7085            };
7086            assert_eq!(gc.ctx().atom_table.bytes(t.operator.get()), op);
7087        };
7088        check(b"component A() renders React.Node {}", b"renders");
7089        check(b"component B() renders? Bar {}", b"renders?");
7090        check(b"component C() renders* Baz {}", b"renders*");
7091    }
7092
7093    /// `async component` and generic `hook` declarations.
7094    #[test]
7095    fn flow_component_async_and_generic_hook() {
7096        use hermes_ast::node::Node;
7097        let mut sm = hermes_support::manager::SourceErrorManager::new();
7098        let mut ctx = comp_ctx();
7099        let gc = ctx.lock();
7100        let stmt0 = comp_parse_stmt_at(
7101            &gc,
7102            &mut sm,
7103            b"async component App() renders null { return null; }\nhook useZ<T>(x: T): T { return x; }",
7104            0,
7105        );
7106        let Node::ComponentDeclaration(c) = stmt0 else {
7107            panic!("expected ComponentDeclaration, got {:?}", stmt0.kind())
7108        };
7109        assert!(c.r#async.get());
7110        assert!(c.renders_type.is_some());
7111
7112        let stmt1 = comp_parse_stmt_at(
7113            &gc,
7114            &mut sm,
7115            b"async component App() renders null { return null; }\nhook useZ<T>(x: T): T { return x; }",
7116            1,
7117        );
7118        let Node::HookDeclaration(h) = stmt1 else {
7119            panic!("expected HookDeclaration, got {:?}", stmt1.kind())
7120        };
7121        assert_eq!(ident_bytes(&gc, h.id), b"useZ");
7122        assert!(h.type_parameters.is_some());
7123        assert!(h.return_type.is_some());
7124        assert!(!h.r#async.get());
7125    }
7126
7127    /// `type C = component(...) renders T;` and `type H = hook(...) => R;`
7128    /// type annotations.
7129    #[test]
7130    fn flow_component_and_hook_type_annotations() {
7131        use hermes_ast::node::Node;
7132        let alias_right = |stmt: &hermes_ast::node::Node<'_>| -> hermes_ast::node::NodeKind {
7133            let Node::TypeAlias(a) = stmt else {
7134                panic!("expected TypeAlias, got {:?}", stmt.kind())
7135            };
7136            a.right.kind()
7137        };
7138
7139        // component type annotation.
7140        {
7141            let mut sm = hermes_support::manager::SourceErrorManager::new();
7142            let mut ctx = comp_ctx();
7143            let gc = ctx.lock();
7144            let stmt = comp_parse_stmt_at(
7145                &gc,
7146                &mut sm,
7147                b"type C = component(foo: string, ...bar: number) renders Baz;",
7148                0,
7149            );
7150            let Node::TypeAlias(a) = stmt else {
7151                panic!("expected TypeAlias, got {:?}", stmt.kind())
7152            };
7153            let Node::ComponentTypeAnnotation(ct) = a.right else {
7154                panic!("expected ComponentTypeAnnotation, got {:?}", a.right.kind())
7155            };
7156            assert_eq!(ct.params.iter().count(), 1);
7157            assert!(ct.rest.is_some());
7158            assert!(ct.renders_type.is_some());
7159        }
7160
7161        // hook type annotation.
7162        {
7163            let mut sm = hermes_support::manager::SourceErrorManager::new();
7164            let mut ctx = comp_ctx();
7165            let gc = ctx.lock();
7166            let stmt = comp_parse_stmt_at(
7167                &gc,
7168                &mut sm,
7169                b"type H = hook(a: number, b: string) => void;",
7170                0,
7171            );
7172            assert_eq!(alias_right(stmt), hermes_ast::node::NodeKind::HookTypeAnnotation);
7173            let Node::TypeAlias(a) = stmt else { unreachable!() };
7174            let Node::HookTypeAnnotation(h) = a.right else {
7175                panic!("expected HookTypeAnnotation, got {:?}", a.right.kind())
7176            };
7177            assert_eq!(h.params.iter().count(), 2);
7178            assert!(h.rest.is_none());
7179        }
7180    }
7181
7182    /// A hook type annotation rejects a `this` constraint.
7183    #[test]
7184    fn flow_hook_type_rejects_this() {
7185        use hermes_ast::context::Context;
7186        use hermes_support::manager::SourceErrorManager;
7187        let src = b"type H = hook(this: number) => void;";
7188        let mut sm = SourceErrorManager::new();
7189        let buf_id = sm.add_buffer_bytes("input", src);
7190        let mut ctx = Context::new();
7191        ctx.set_parse_flow(true);
7192        ctx.set_parse_flow_component_syntax(true);
7193        let gc = ctx.lock();
7194        let atoms = &gc.ctx().atom_table;
7195        let lexer = crate::lexer::JSLexer::new(
7196            buf_id,
7197            &mut sm,
7198            atoms,
7199            crate::lexer::GrammarContext::AllowRegExp,
7200        );
7201        let mut parser = JSParserImpl::new(&gc, lexer);
7202        let _ = parser.parse();
7203        assert!(
7204            parser.error_count_pub() >= 1,
7205            "hook type 'this' constraint must error"
7206        );
7207    }
7208
7209    /// `async component`/`async hook` *declarations* are not allowed inside a
7210    /// `declare`; but here verify the simpler gate: with component syntax OFF,
7211    /// `component`/`hook`/`renders` stay plain identifiers.
7212    #[test]
7213    fn flow_component_syntax_gated_off() {
7214        // Without the component-syntax flag, `component Foo() {}` is NOT a
7215        // declaration; it is parsed as the identifier `component` followed by
7216        // `Foo`, which is a syntax error (matching hermesc's exit 2 under
7217        // `-parse-flow` alone).
7218        assert_parse_has_errors_impl(
7219            b"component Foo() {}",
7220            "component needs component-syntax flag",
7221            /* parse_flow */ true,
7222        );
7223        // But `component` remains a valid identifier in an expression.
7224        let mut sm = hermes_support::manager::SourceErrorManager::new();
7225        let mut ctx = hermes_ast::context::Context::new();
7226        ctx.set_parse_flow(true);
7227        let gc = ctx.lock();
7228        let stmt = parse_one_stmt(&gc, &mut sm, b"var component = 1;");
7229        assert!(matches!(stmt, hermes_ast::node::Node::VariableDeclaration(_)));
7230    }
7231
7232    /// No-leak spot checks: with Flow parsing DISABLED the new sites must not
7233    /// consume type syntax (each input still errors, exactly as hermesc does
7234    /// without `-parse-flow`).
7235    #[test]
7236    fn flow_p54_no_leak() {
7237        assert_parse_has_errors(
7238            b"class C extends B<T> {}",
7239            "super type args need Flow",
7240        );
7241        assert_parse_has_errors(
7242            b"function f(): T { return 1; }",
7243            "return type needs Flow",
7244        );
7245        assert_parse_has_errors(b"var a: T;", "binding annotation needs Flow");
7246        assert_parse_has_errors(
7247            b"var o = { m<T>() {} };",
7248            "object-method type params need Flow",
7249        );
7250    }
7251
7252    // -----------------------------------------------------------------------
7253    // P6.4 — Flow record declarations + expressions.
7254    // -----------------------------------------------------------------------
7255
7256    /// Build a records-enabled `Context` (Flow + ambiguous + records). The
7257    /// ambiguous-expression grammar is set because hermesc `-parse-flow`
7258    /// defaults to `ParseFlowSetting::ALL` (ambiguous on), and the record
7259    /// EXPRESSION type-args speculation (`ns.Maker<T> {…}`) is gated on it.
7260    fn rec_ctx() -> hermes_ast::context::Context<'static> {
7261        let mut ctx = hermes_ast::context::Context::new();
7262        ctx.set_parse_flow(true);
7263        ctx.set_parse_flow_ambiguous(true);
7264        ctx.set_parse_flow_records(true);
7265        ctx
7266    }
7267
7268    /// Parse `src` with records enabled, expect zero errors, return statement
7269    /// `idx`. Reuses the component-test helper (which only needs the flags set
7270    /// on the passed `gc`).
7271    fn rec_parse_stmt_at<'gc>(
7272        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
7273        sm: &mut hermes_support::manager::SourceErrorManager,
7274        src: &[u8],
7275        idx: usize,
7276    ) -> &'gc hermes_ast::node::Node<'gc> {
7277        comp_parse_stmt_at(gc, sm, src, idx)
7278    }
7279
7280    /// `record Foo {}` — an empty record declaration.
7281    #[test]
7282    fn flow_record_empty() {
7283        use hermes_ast::node::Node;
7284        let mut sm = hermes_support::manager::SourceErrorManager::new();
7285        let mut ctx = rec_ctx();
7286        let gc = ctx.lock();
7287        let stmt = rec_parse_stmt_at(&gc, &mut sm, b"record Foo {}", 0);
7288        let Node::RecordDeclaration(r) = stmt else {
7289            panic!("expected RecordDeclaration, got {:?}", stmt.kind())
7290        };
7291        assert_eq!(ident_bytes(&gc, r.id), b"Foo");
7292        assert!(r.type_parameters.is_none(), "no type params");
7293        assert_eq!(r.implements.iter().count(), 0, "no implements");
7294        let Node::RecordDeclarationBody(body) = r.body else {
7295            panic!("expected RecordDeclarationBody, got {:?}", r.body.kind())
7296        };
7297        assert_eq!(body.elements.iter().count(), 0, "empty body");
7298    }
7299
7300    /// `record R<T> implements I, J<K> { ... }` — type params + an implements
7301    /// clause carrying type-args, plus property / static-property / method /
7302    /// async-generator-method body elements.
7303    #[test]
7304    fn flow_record_full() {
7305        use hermes_ast::node::Node;
7306        let mut sm = hermes_support::manager::SourceErrorManager::new();
7307        let mut ctx = rec_ctx();
7308        let gc = ctx.lock();
7309        let stmt = rec_parse_stmt_at(
7310            &gc,
7311            &mut sm,
7312            b"record Point<T> implements I, J<K> {\n\
7313              x: number, y: T,\n\
7314              static origin: Point = mk(),\n\
7315              dist(o: Point): number { return 0; }\n\
7316              async *gen<U>(): U {}\n\
7317            }",
7318            0,
7319        );
7320        let Node::RecordDeclaration(r) = stmt else {
7321            panic!("expected RecordDeclaration, got {:?}", stmt.kind())
7322        };
7323        assert_eq!(ident_bytes(&gc, r.id), b"Point");
7324        assert!(r.type_parameters.is_some(), "has type params");
7325
7326        // Two implements entries; the second carries type-args.
7327        let impls: Vec<_> = r.implements.iter().collect();
7328        assert_eq!(impls.len(), 2, "two implements entries");
7329        let Node::RecordDeclarationImplements(i0) = impls[0] else {
7330            panic!("expected RecordDeclarationImplements")
7331        };
7332        assert_eq!(ident_bytes(&gc, i0.id), b"I");
7333        assert!(i0.type_arguments.is_none(), "I has no type-args");
7334        let Node::RecordDeclarationImplements(i1) = impls[1] else {
7335            panic!("expected RecordDeclarationImplements")
7336        };
7337        assert_eq!(ident_bytes(&gc, i1.id), b"J");
7338        assert!(i1.type_arguments.is_some(), "J<K> has type-args");
7339
7340        let Node::RecordDeclarationBody(body) = r.body else {
7341            panic!("expected RecordDeclarationBody")
7342        };
7343        let elems: Vec<_> = body.elements.iter().collect();
7344        assert_eq!(elems.len(), 5, "x, y, static origin, dist, gen");
7345
7346        // x: number — a plain property with no default.
7347        let Node::RecordDeclarationProperty(p_x) = elems[0] else {
7348            panic!("expected RecordDeclarationProperty, got {:?}", elems[0].kind())
7349        };
7350        assert_eq!(ident_bytes(&gc, p_x.key), b"x");
7351        assert!(p_x.default_value.is_none(), "x has no initializer");
7352
7353        // static origin: Point = mk() — a static property (value required).
7354        let Node::RecordDeclarationStaticProperty(p_origin) = elems[2] else {
7355            panic!(
7356                "expected RecordDeclarationStaticProperty, got {:?}",
7357                elems[2].kind()
7358            )
7359        };
7360        assert_eq!(ident_bytes(&gc, p_origin.key), b"origin");
7361
7362        // dist(o: Point): number {...} — a method.
7363        let Node::MethodDefinition(m_dist) = elems[3] else {
7364            panic!("expected MethodDefinition, got {:?}", elems[3].kind())
7365        };
7366        assert!(!m_dist.r#static.get(), "dist is not static");
7367        let Node::FunctionExpression(f_dist) = m_dist.value else {
7368            panic!("expected FunctionExpression")
7369        };
7370        assert!(!f_dist.generator.get() && !f_dist.r#async.get());
7371        assert!(f_dist.return_type.is_some(), "dist has a return type");
7372
7373        // async *gen<U>(): U {} — an async generator method with type params.
7374        let Node::MethodDefinition(m_gen) = elems[4] else {
7375            panic!("expected MethodDefinition, got {:?}", elems[4].kind())
7376        };
7377        let Node::FunctionExpression(f_gen) = m_gen.value else {
7378            panic!("expected FunctionExpression")
7379        };
7380        assert!(f_gen.generator.get(), "gen is a generator");
7381        assert!(f_gen.r#async.get(), "gen is async");
7382        assert!(f_gen.type_parameters.is_some(), "gen has type params");
7383    }
7384
7385    /// `Point { x: 1 }` — a record EXPRESSION with an Identifier constructor.
7386    #[test]
7387    fn flow_record_expr_ident() {
7388        use hermes_ast::node::Node;
7389        let mut sm = hermes_support::manager::SourceErrorManager::new();
7390        let mut ctx = rec_ctx();
7391        let gc = ctx.lock();
7392        let init = {
7393            let stmt =
7394                rec_parse_stmt_at(&gc, &mut sm, b"const p = Point { x: 1 };", 0);
7395            let Node::VariableDeclaration(vd) = stmt else {
7396                panic!("expected VariableDeclaration")
7397            };
7398            let Node::VariableDeclarator(d) =
7399                vd.declarations.iter().next().unwrap()
7400            else {
7401                panic!("expected VariableDeclarator")
7402            };
7403            d.init.expect("has init")
7404        };
7405        let Node::RecordExpression(re) = init else {
7406            panic!("expected RecordExpression, got {:?}", init.kind())
7407        };
7408        assert_eq!(ident_bytes(&gc, re.record_constructor), b"Point");
7409        assert!(re.type_arguments.is_none(), "no type-args");
7410        let Node::RecordExpressionProperties(props) = re.properties else {
7411            panic!("expected RecordExpressionProperties")
7412        };
7413        assert_eq!(props.properties.iter().count(), 1, "one property");
7414    }
7415
7416    /// `ns.Maker<T> { a: 3 }` — a record expression with a MemberExpression
7417    /// constructor AND type-args (exercises the LHS-tail commit-condition).
7418    #[test]
7419    fn flow_record_expr_member_typeargs() {
7420        use hermes_ast::node::Node;
7421        let mut sm = hermes_support::manager::SourceErrorManager::new();
7422        let mut ctx = rec_ctx();
7423        let gc = ctx.lock();
7424        let init = {
7425            let stmt = rec_parse_stmt_at(
7426                &gc,
7427                &mut sm,
7428                b"const q = ns.Maker<T> { a: 3 };",
7429                0,
7430            );
7431            let Node::VariableDeclaration(vd) = stmt else {
7432                panic!("expected VariableDeclaration")
7433            };
7434            let Node::VariableDeclarator(d) =
7435                vd.declarations.iter().next().unwrap()
7436            else {
7437                panic!("expected VariableDeclarator")
7438            };
7439            d.init.expect("has init")
7440        };
7441        let Node::RecordExpression(re) = init else {
7442            panic!("expected RecordExpression, got {:?}", init.kind())
7443        };
7444        assert!(
7445            matches!(re.record_constructor, Node::MemberExpression(_)),
7446            "MemberExpression constructor"
7447        );
7448        assert!(re.type_arguments.is_some(), "ns.Maker<T> has type-args");
7449    }
7450
7451    /// `checkRecordExpressionFlow` rejects a constructor whose name begins with
7452    /// a lowercase ascii letter: `point { x: 1 }` is a block-bodied arrow's
7453    /// label-or-block, NOT a record — it must NOT parse as a RecordExpression.
7454    /// With records enabled but a lowercase ctor, hermesc treats `point` as an
7455    /// identifier expression statement followed by a block; the Rust must too.
7456    #[test]
7457    fn flow_record_expr_lowercase_rejected() {
7458        use hermes_ast::node::Node;
7459        let mut sm = hermes_support::manager::SourceErrorManager::new();
7460        let mut ctx = rec_ctx();
7461        let gc = ctx.lock();
7462        // `point` (lowercase) then a block `{ x }`. Not a record expression;
7463        // `point` is an expression statement, `{ x }` a block statement.
7464        let stmt = rec_parse_stmt_at(&gc, &mut sm, b"point\n{ x }", 0);
7465        assert!(
7466            !matches!(stmt, Node::RecordExpression(_)),
7467            "lowercase ctor must not form a RecordExpression"
7468        );
7469    }
7470
7471    /// With records DISABLED, `record R {}` must NOT parse as a record (it is a
7472    /// plain identifier `record` then `R` — an error), confirming the gate.
7473    #[test]
7474    fn flow_record_disabled_is_not_record() {
7475        use hermes_ast::context::Context;
7476        let mut sm = hermes_support::manager::SourceErrorManager::new();
7477        let mut ctx = Context::new();
7478        ctx.set_parse_flow(true); // Flow on, records OFF.
7479        let gc = ctx.lock();
7480        let buf_id = sm.add_buffer_bytes("input", b"record R {}");
7481        let atoms = &gc.ctx().atom_table;
7482        let lexer = crate::lexer::JSLexer::new(
7483            buf_id,
7484            &mut sm,
7485            atoms,
7486            crate::lexer::GrammarContext::AllowRegExp,
7487        );
7488        let mut parser = JSParserImpl::new(&gc, lexer);
7489        let _ = parser.parse();
7490        assert!(
7491            parser.error_count_pub() > 0,
7492            "record disabled: `record R {{}}` must report a syntax error"
7493        );
7494    }
7495
7496    // -----------------------------------------------------------------------
7497    // P6.5: Flow `match` expressions and statements.
7498    // -----------------------------------------------------------------------
7499
7500    /// Build a `Context` with Flow + `parse_flow_match` enabled.
7501    fn match_ctx() -> hermes_ast::context::Context<'static> {
7502        use hermes_ast::context::Context;
7503        let mut ctx = Context::new();
7504        ctx.set_parse_flow(true);
7505        ctx.set_parse_flow_match(true);
7506        ctx
7507    }
7508
7509    /// Parse `src` with the match flag on; expect zero errors; return the
7510    /// statement at `idx`.
7511    fn match_parse_stmt_at<'gc>(
7512        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
7513        sm: &mut hermes_support::manager::SourceErrorManager,
7514        src: &[u8],
7515        idx: usize,
7516    ) -> &'gc hermes_ast::node::Node<'gc> {
7517        let buf_id = sm.add_buffer_bytes("input", src);
7518        let atoms = &gc.ctx().atom_table;
7519        let lexer = crate::lexer::JSLexer::new(
7520            buf_id,
7521            sm,
7522            atoms,
7523            crate::lexer::GrammarContext::AllowRegExp,
7524        );
7525        let mut parser = JSParserImpl::new(gc, lexer);
7526        let program = parser.parse().expect("parse succeeded");
7527        assert_eq!(parser.error_count_pub(), 0, "zero errors for {src:?}");
7528        if let hermes_ast::node::Node::Program(p) = program {
7529            return p.body.iter().nth(idx).expect("has enough statements");
7530        }
7531        panic!("expected Program");
7532    }
7533
7534    /// Extract the single `MatchExpression` from `const r = <match-expr>;`.
7535    fn match_expr_from<'gc>(
7536        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
7537        sm: &mut hermes_support::manager::SourceErrorManager,
7538        src: &[u8],
7539    ) -> &'gc hermes_ast::node::Node<'gc> {
7540        use hermes_ast::node::Node;
7541        let stmt = match_parse_stmt_at(gc, sm, src, 0);
7542        let Node::VariableDeclaration(vd) = stmt else {
7543            panic!("expected VariableDeclaration, got {:?}", stmt.kind())
7544        };
7545        let decl = vd.declarations.iter().next().expect("one declarator");
7546        let Node::VariableDeclarator(d) = decl else {
7547            panic!("expected VariableDeclarator")
7548        };
7549        d.init.expect("has init")
7550    }
7551
7552    /// `match (x) { 1 => 'a', _ => 'c' }` is a `MatchExpression` whose cases
7553    /// carry `MatchLiteralPattern`/`MatchWildcardPattern` and expression bodies.
7554    #[test]
7555    fn flow_match_expression_basic() {
7556        use hermes_ast::node::Node;
7557        let mut sm = hermes_support::manager::SourceErrorManager::new();
7558        let mut ctx = match_ctx();
7559        let gc = ctx.lock();
7560        let expr =
7561            match_expr_from(&gc, &mut sm, b"const r = match (x) { 1 => 'a', _ => 'c' };");
7562        let Node::MatchExpression(m) = expr else {
7563            panic!("expected MatchExpression, got {:?}", expr.kind())
7564        };
7565        assert!(matches!(m.argument, Node::Identifier(_)), "arg is `x`");
7566        let mut it = m.cases.iter();
7567        let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
7568            panic!("expected MatchExpressionCase")
7569        };
7570        assert!(
7571            matches!(c0.pattern, Node::MatchLiteralPattern(_)),
7572            "first case is a literal pattern"
7573        );
7574        assert!(c0.guard.is_none(), "no guard");
7575        let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
7576            panic!("expected MatchExpressionCase")
7577        };
7578        assert!(
7579            matches!(c1.pattern, Node::MatchWildcardPattern(_)),
7580            "second case is the wildcard `_`"
7581        );
7582        assert!(it.next().is_none(), "exactly two cases");
7583    }
7584
7585    /// A `match` statement: block bodies, optional commas.
7586    #[test]
7587    fn flow_match_statement_basic() {
7588        use hermes_ast::node::Node;
7589        let mut sm = hermes_support::manager::SourceErrorManager::new();
7590        let mut ctx = match_ctx();
7591        let gc = ctx.lock();
7592        let stmt =
7593            match_parse_stmt_at(&gc, &mut sm, b"match (x) { 1 => { f(); } _ => { g(); } }", 0);
7594        let Node::MatchStatement(m) = stmt else {
7595            panic!("expected MatchStatement, got {:?}", stmt.kind())
7596        };
7597        let mut it = m.cases.iter();
7598        let Node::MatchStatementCase(c0) = it.next().unwrap() else {
7599            panic!("expected MatchStatementCase")
7600        };
7601        assert!(
7602            matches!(c0.body, Node::BlockStatement(_)),
7603            "statement case body is a block"
7604        );
7605        assert!(it.next().is_some(), "second case present");
7606    }
7607
7608    /// `match(1, 2)` is a plain CallExpression even with the flag on (no `{`),
7609    /// and `match (foo)(bar)` is a chained CallExpression — the call path.
7610    #[test]
7611    fn flow_match_call_not_match() {
7612        use hermes_ast::node::Node;
7613        let mut sm = hermes_support::manager::SourceErrorManager::new();
7614        let mut ctx = match_ctx();
7615        let gc = ctx.lock();
7616
7617        let stmt0 = match_parse_stmt_at(&gc, &mut sm, b"match(1, 2);\nmatch (foo)(bar);", 0);
7618        let Node::ExpressionStatement(es0) = stmt0 else {
7619            panic!("expected ExpressionStatement")
7620        };
7621        let Node::CallExpression(call) = es0.expression else {
7622            panic!("expected CallExpression, got {:?}", es0.expression.kind())
7623        };
7624        // The callee is the `match` identifier.
7625        assert!(matches!(call.callee, Node::Identifier(_)), "callee is `match`");
7626
7627        let stmt1 = match_parse_stmt_at(&gc, &mut sm, b"match(1, 2);\nmatch (foo)(bar);", 1);
7628        let Node::ExpressionStatement(es1) = stmt1 else {
7629            panic!("expected ExpressionStatement")
7630        };
7631        // `match (foo)(bar)` → CallExpression whose callee is itself a
7632        // CallExpression `match(foo)`.
7633        let Node::CallExpression(outer) = es1.expression else {
7634            panic!("expected CallExpression")
7635        };
7636        assert!(
7637            matches!(outer.callee, Node::CallExpression(_)),
7638            "outer callee is `match(foo)`"
7639        );
7640    }
7641
7642    /// A newline between `match` and `(` means this is NOT a match construct
7643    /// (C++ `lookahead1(None)` defaults to `RequireNoNewLine = true`). hermesc
7644    /// rejects `match\n(x) { _ => 1 }` with `';' expected`; the parser must too
7645    /// (it must not silently parse a `MatchExpression`/`MatchStatement`).
7646    #[test]
7647    fn flow_match_newline_is_not_match() {
7648        let mut sm = hermes_support::manager::SourceErrorManager::new();
7649        let mut ctx = match_ctx();
7650        let gc = ctx.lock();
7651        let buf_id = sm.add_buffer_bytes("input", b"match\n(x) { _ => 1 }\n");
7652        let atoms = &gc.ctx().atom_table;
7653        let lexer = crate::lexer::JSLexer::new(
7654            buf_id,
7655            &mut sm,
7656            atoms,
7657            crate::lexer::GrammarContext::AllowRegExp,
7658        );
7659        let mut parser = JSParserImpl::new(&gc, lexer);
7660        let _ = parser.parse();
7661        assert!(
7662            parser.error_count_pub() > 0,
7663            "`match\\n(x) {{…}}` must error (newline blocks the match), not parse as a match"
7664        );
7665    }
7666
7667    /// Both the statement and the expression forms can be distinguished by
7668    /// context: `const r = match (x) {…}` is an expression; bare
7669    /// `match (x) {…}` is a statement.
7670    #[test]
7671    fn flow_match_expr_vs_stmt() {
7672        use hermes_ast::node::Node;
7673        let mut sm = hermes_support::manager::SourceErrorManager::new();
7674        let mut ctx = match_ctx();
7675        let gc = ctx.lock();
7676        let e = match_expr_from(&gc, &mut sm, b"const r = match (x) { _ => 1 };");
7677        assert!(matches!(e, Node::MatchExpression(_)), "expr form");
7678        let s = match_parse_stmt_at(&gc, &mut sm, b"match (x) { _ => { y; } }", 0);
7679        assert!(matches!(s, Node::MatchStatement(_)), "stmt form");
7680    }
7681
7682    /// Object + array patterns, including the `...const rest` rest binding.
7683    #[test]
7684    fn flow_match_object_array_patterns() {
7685        use hermes_ast::node::Node;
7686        let mut sm = hermes_support::manager::SourceErrorManager::new();
7687        let mut ctx = match_ctx();
7688        let gc = ctx.lock();
7689        let expr = match_expr_from(
7690            &gc,
7691            &mut sm,
7692            b"const r = match (x) { {a: 1, b: _} => 1, [1, 2, ...const rest] => 2, _ => 3 };",
7693        );
7694        let Node::MatchExpression(m) = expr else {
7695            panic!("expected MatchExpression")
7696        };
7697        let mut it = m.cases.iter();
7698        let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
7699            panic!("case 0")
7700        };
7701        let Node::MatchObjectPattern(obj) = c0.pattern else {
7702            panic!("expected MatchObjectPattern, got {:?}", c0.pattern.kind())
7703        };
7704        assert_eq!(obj.properties.iter().count(), 2, "two object props");
7705        assert!(obj.rest.is_none(), "no object rest");
7706        let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
7707            panic!("case 1")
7708        };
7709        let Node::MatchArrayPattern(arr) = c1.pattern else {
7710            panic!("expected MatchArrayPattern, got {:?}", c1.pattern.kind())
7711        };
7712        assert_eq!(arr.elements.iter().count(), 2, "two array elements");
7713        let rest = arr.rest.expect("array rest present");
7714        let Node::MatchRestPattern(rp) = rest else {
7715            panic!("expected MatchRestPattern")
7716        };
7717        assert!(rp.argument.is_some(), "rest binds `const rest`");
7718    }
7719
7720    /// Or-patterns, `const`/`let` bindings, and an `if` guard.
7721    #[test]
7722    fn flow_match_or_and_guard() {
7723        use hermes_ast::node::Node;
7724        let mut sm = hermes_support::manager::SourceErrorManager::new();
7725        let mut ctx = match_ctx();
7726        let gc = ctx.lock();
7727        let expr = match_expr_from(
7728            &gc,
7729            &mut sm,
7730            b"const r = match (x) { 1 | 2 | 3 => 'low', const y if (y > 0) => 'pos', _ => 'z' };",
7731        );
7732        let Node::MatchExpression(m) = expr else {
7733            panic!("expected MatchExpression")
7734        };
7735        let mut it = m.cases.iter();
7736        let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
7737            panic!("case 0")
7738        };
7739        let Node::MatchOrPattern(or) = c0.pattern else {
7740            panic!("expected MatchOrPattern, got {:?}", c0.pattern.kind())
7741        };
7742        assert_eq!(or.patterns.iter().count(), 3, "three or-alternatives");
7743        let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
7744            panic!("case 1")
7745        };
7746        assert!(
7747            matches!(c1.pattern, Node::MatchBindingPattern(_)),
7748            "second case is a `const y` binding"
7749        );
7750        assert!(c1.guard.is_some(), "second case has an `if` guard");
7751    }
7752
7753    /// Member pattern (`Foo.Bar`), unary pattern (`-2`), and an instance
7754    /// pattern (`Status{value: const v}`).
7755    #[test]
7756    fn flow_match_member_unary_instance() {
7757        use hermes_ast::node::Node;
7758        let mut sm = hermes_support::manager::SourceErrorManager::new();
7759        let mut ctx = match_ctx();
7760        let gc = ctx.lock();
7761        let expr = match_expr_from(
7762            &gc,
7763            &mut sm,
7764            b"const r = match (x) { Foo.Bar => 1, -2 => 2, Status{value: const v} => v, _ => 0 };",
7765        );
7766        let Node::MatchExpression(m) = expr else {
7767            panic!("expected MatchExpression")
7768        };
7769        let mut it = m.cases.iter();
7770        let Node::MatchExpressionCase(c0) = it.next().unwrap() else {
7771            panic!("case 0")
7772        };
7773        assert!(
7774            matches!(c0.pattern, Node::MatchMemberPattern(_)),
7775            "first case is a member pattern, got {:?}",
7776            c0.pattern.kind()
7777        );
7778        let Node::MatchExpressionCase(c1) = it.next().unwrap() else {
7779            panic!("case 1")
7780        };
7781        let Node::MatchUnaryPattern(u) = c1.pattern else {
7782            panic!("expected MatchUnaryPattern, got {:?}", c1.pattern.kind())
7783        };
7784        assert_eq!(
7785            gc.ctx().atom_table.bytes(u.operator.get()),
7786            b"-",
7787            "unary operator is `-`"
7788        );
7789        let Node::MatchExpressionCase(c2) = it.next().unwrap() else {
7790            panic!("case 2")
7791        };
7792        let Node::MatchInstancePattern(inst) = c2.pattern else {
7793            panic!("expected MatchInstancePattern, got {:?}", c2.pattern.kind())
7794        };
7795        assert!(
7796            matches!(inst.properties, Node::MatchInstanceObjectPattern(_)),
7797            "instance properties are a MatchInstanceObjectPattern"
7798        );
7799    }
7800
7801    /// The `as` binding pattern wrapping an or-pattern group: `(1 | 2) as const k`.
7802    #[test]
7803    fn flow_match_as_pattern() {
7804        use hermes_ast::node::Node;
7805        let mut sm = hermes_support::manager::SourceErrorManager::new();
7806        let mut ctx = match_ctx();
7807        let gc = ctx.lock();
7808        let expr =
7809            match_expr_from(&gc, &mut sm, b"const r = match (x) { (1 | 2) as const k => k, _ => 0 };");
7810        let Node::MatchExpression(m) = expr else {
7811            panic!("expected MatchExpression")
7812        };
7813        let c0 = m.cases.iter().next().unwrap();
7814        let Node::MatchExpressionCase(c0) = c0 else {
7815            panic!("case 0")
7816        };
7817        let Node::MatchAsPattern(asp) = c0.pattern else {
7818            panic!("expected MatchAsPattern, got {:?}", c0.pattern.kind())
7819        };
7820        // The group `(1 | 2)` emits no wrapper, so the inner pattern is the
7821        // or-pattern directly.
7822        assert!(
7823            matches!(asp.pattern, Node::MatchOrPattern(_)),
7824            "as-pattern wraps a group-elided or-pattern"
7825        );
7826        assert!(
7827            matches!(asp.target, Node::MatchBindingPattern(_)),
7828            "as-target is a `const k` binding"
7829        );
7830    }
7831
7832    /// A bare `...rest` (no binding keyword) inside an array pattern is a parse
7833    /// error — faithful to hermesc.
7834    #[test]
7835    fn flow_match_rest_needs_binding() {
7836        let mut sm = hermes_support::manager::SourceErrorManager::new();
7837        let mut ctx = match_ctx();
7838        let gc = ctx.lock();
7839        let buf_id =
7840            sm.add_buffer_bytes("input", b"const r = match (x) { [...rest] => 1, _ => 0 };");
7841        let atoms = &gc.ctx().atom_table;
7842        let lexer = crate::lexer::JSLexer::new(
7843            buf_id,
7844            &mut sm,
7845            atoms,
7846            crate::lexer::GrammarContext::AllowRegExp,
7847        );
7848        let mut parser = JSParserImpl::new(&gc, lexer);
7849        let _ = parser.parse();
7850        assert!(
7851            parser.error_count_pub() > 0,
7852            "`...rest` without a binding keyword must error"
7853        );
7854    }
7855
7856    /// With the match flag OFF, `match` is a plain identifier: `match(x)` is a
7857    /// call, and `const r = match;` is an identifier reference.
7858    #[test]
7859    fn flow_match_disabled_is_identifier() {
7860        use hermes_ast::context::Context;
7861        use hermes_ast::node::Node;
7862        let mut sm = hermes_support::manager::SourceErrorManager::new();
7863        // Flow on, match OFF.
7864        let mut ctx = Context::new();
7865        ctx.set_parse_flow(true);
7866        let gc = ctx.lock();
7867        let buf_id = sm.add_buffer_bytes("input", b"const r = match;\nmatch(x);");
7868        let atoms = &gc.ctx().atom_table;
7869        let lexer = crate::lexer::JSLexer::new(
7870            buf_id,
7871            &mut sm,
7872            atoms,
7873            crate::lexer::GrammarContext::AllowRegExp,
7874        );
7875        let mut parser = JSParserImpl::new(&gc, lexer);
7876        let program = parser.parse().expect("parses");
7877        assert_eq!(parser.error_count_pub(), 0, "no errors when match is off");
7878        let Node::Program(p) = program else {
7879            panic!("expected Program")
7880        };
7881        // First statement: `const r = match;` — init is a bare Identifier.
7882        let mut body = p.body.iter();
7883        let Node::VariableDeclaration(vd) = body.next().unwrap() else {
7884            panic!("expected VariableDeclaration")
7885        };
7886        let Node::VariableDeclarator(d) = vd.declarations.iter().next().unwrap() else {
7887            panic!("expected VariableDeclarator")
7888        };
7889        assert!(
7890            matches!(d.init, Some(Node::Identifier(_))),
7891            "`match` is a plain identifier reference when the flag is off"
7892        );
7893    }
7894
7895    // -----------------------------------------------------------------------
7896    // P6.6: declare family + import/export type clauses + Flow default exports
7897    // -----------------------------------------------------------------------
7898
7899    /// Parse `src` with Flow on (and component syntax optionally on), returning
7900    /// the program's whole body and asserting zero errors.
7901    fn flow_parse_body<'gc>(
7902        gc: &'gc hermes_ast::context::GCLock<'_, '_>,
7903        sm: &mut hermes_support::manager::SourceErrorManager,
7904        src: &[u8],
7905        components: bool,
7906    ) -> Vec<&'gc hermes_ast::node::Node<'gc>> {
7907        let _ = components; // ctx is already configured by the caller
7908        let buf_id = sm.add_buffer_bytes("input", src);
7909        let atoms = &gc.ctx().atom_table;
7910        let lexer = crate::lexer::JSLexer::new(
7911            buf_id,
7912            sm,
7913            atoms,
7914            crate::lexer::GrammarContext::AllowRegExp,
7915        );
7916        let mut parser = JSParserImpl::new(gc, lexer);
7917        let program = parser.parse().expect("parse succeeded");
7918        assert_eq!(
7919            parser.error_count_pub(),
7920            0,
7921            "zero errors for {:?}",
7922            String::from_utf8_lossy(src)
7923        );
7924        if let hermes_ast::node::Node::Program(p) = program {
7925            return p.body.iter().collect();
7926        }
7927        panic!("expected Program");
7928    }
7929
7930    /// Each `declare` statement form parses to the right node kind.
7931    #[test]
7932    fn flow_declare_forms() {
7933        use hermes_ast::context::Context;
7934        use hermes_ast::node::Node;
7935        use hermes_support::manager::SourceErrorManager;
7936
7937        let check = |src: &[u8], pred: fn(&Node) -> bool| {
7938            let mut sm = SourceErrorManager::new();
7939            let mut ctx = Context::new();
7940            ctx.set_parse_flow(true);
7941            let gc = ctx.lock();
7942            let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
7943            assert!(pred(stmt), "wrong node for {:?}: {:?}", String::from_utf8_lossy(src), stmt.kind());
7944        };
7945
7946        check(b"declare function foo(x: number): string;", |n| {
7947            matches!(n, Node::DeclareFunction(_))
7948        });
7949        check(b"declare var x: number;", |n| {
7950            matches!(n, Node::DeclareVariable(_))
7951        });
7952        check(b"declare type T = number;", |n| {
7953            matches!(n, Node::DeclareTypeAlias(_))
7954        });
7955        check(b"declare interface I { foo(): void }", |n| {
7956            matches!(n, Node::DeclareInterface(_))
7957        });
7958        check(
7959            b"declare class C<T> extends B mixins M implements I { x: number; }",
7960            |n| matches!(n, Node::DeclareClass(_)),
7961        );
7962        check(b"declare module 'x' { declare var y: number; }", |n| {
7963            matches!(n, Node::DeclareModule(_))
7964        });
7965        check(b"declare module.exports: { a: number };", |n| {
7966            matches!(n, Node::DeclareModuleExports(_))
7967        });
7968        check(b"declare namespace NS { declare var z: string; }", |n| {
7969            matches!(n, Node::DeclareNamespace(_))
7970        });
7971        check(b"declare opaque type O: number;", |n| {
7972            matches!(n, Node::DeclareOpaqueType(_))
7973        });
7974        check(b"declare enum E { A, B }", |n| {
7975            matches!(n, Node::DeclareEnum(_))
7976        });
7977    }
7978
7979    /// The various `declare export ...` arms wrap their declaration (or
7980    /// specifiers) in a DeclareExportDeclaration / DeclareExportAllDeclaration,
7981    /// with `default` set only for `declare export default`.
7982    #[test]
7983    fn flow_declare_export_forms() {
7984        use hermes_ast::context::Context;
7985        use hermes_ast::node::Node;
7986        use hermes_support::manager::SourceErrorManager;
7987
7988        let decl_of = |src: &[u8]| -> bool {
7989            // returns the `default` flag of a DeclareExportDeclaration
7990            let mut sm = SourceErrorManager::new();
7991            let mut ctx = Context::new();
7992            ctx.set_parse_flow(true);
7993            let gc = ctx.lock();
7994            let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
7995            let Node::DeclareExportDeclaration(d) = stmt else {
7996                panic!("expected DeclareExportDeclaration for {:?}, got {:?}",
7997                    String::from_utf8_lossy(src), stmt.kind())
7998            };
7999            d.default.get()
8000        };
8001
8002        assert!(!decl_of(b"declare export function f(): void;"));
8003        assert!(decl_of(b"declare export default number;"));
8004        assert!(!decl_of(b"declare export opaque type T2: number;"));
8005
8006        // `declare export interface I2` uses the no-arg interface parser →
8007        // its declaration is a plain InterfaceDeclaration (not DeclareInterface).
8008        {
8009            let mut sm = SourceErrorManager::new();
8010            let mut ctx = Context::new();
8011            ctx.set_parse_flow(true);
8012            let gc = ctx.lock();
8013            let stmt = flow_parse_stmt_at(
8014                &gc,
8015                &mut sm,
8016                b"declare export interface I2 { a: number }",
8017                0,
8018            );
8019            let Node::DeclareExportDeclaration(d) = stmt else {
8020                panic!("expected DeclareExportDeclaration")
8021            };
8022            assert!(
8023                matches!(d.declaration, Some(Node::InterfaceDeclaration(_))),
8024                "declare export interface wraps an InterfaceDeclaration"
8025            );
8026        }
8027
8028        // `declare export * from 'mod'` → DeclareExportAllDeclaration.
8029        {
8030            let mut sm = SourceErrorManager::new();
8031            let mut ctx = Context::new();
8032            ctx.set_parse_flow(true);
8033            let gc = ctx.lock();
8034            let stmt2 = flow_parse_stmt_at(
8035                &gc,
8036                &mut sm,
8037                b"declare export * from 'mod';",
8038                0,
8039            );
8040            assert!(matches!(stmt2, Node::DeclareExportAllDeclaration(_)));
8041        }
8042    }
8043
8044    /// `import type`/`import typeof` set the declaration's importKind, and the
8045    /// per-specifier `type`/`typeof` forms set each specifier's importKind.
8046    #[test]
8047    fn flow_import_type_kinds() {
8048        use hermes_ast::context::Context;
8049        use hermes_ast::node::Node;
8050        use hermes_support::manager::SourceErrorManager;
8051
8052        let import_kind = |src: &[u8]| -> Vec<u8> {
8053            let mut sm = SourceErrorManager::new();
8054            let mut ctx = Context::new();
8055            ctx.set_parse_flow(true);
8056            let gc = ctx.lock();
8057            let stmt = flow_parse_stmt_at(&gc, &mut sm, src, 0);
8058            let Node::ImportDeclaration(d) = stmt else {
8059                panic!("expected ImportDeclaration for {:?}, got {:?}",
8060                    String::from_utf8_lossy(src), stmt.kind())
8061            };
8062            gc.ctx().atom_table.bytes(d.import_kind.get()).to_vec()
8063        };
8064
8065        assert_eq!(import_kind(b"import type {A} from 'x';"), b"type");
8066        assert_eq!(import_kind(b"import typeof B from 'x';"), b"typeof");
8067        assert_eq!(import_kind(b"import {A} from 'x';"), b"value");
8068
8069        // Per-specifier kinds in `import {type A2, typeof C} from 'x';`.
8070        let mut sm = SourceErrorManager::new();
8071        let mut ctx = Context::new();
8072        ctx.set_parse_flow(true);
8073        let gc = ctx.lock();
8074        let stmt = flow_parse_stmt_at(
8075            &gc,
8076            &mut sm,
8077            b"import {type A2, typeof C} from 'x';",
8078            0,
8079        );
8080        let Node::ImportDeclaration(d) = stmt else {
8081            panic!("expected ImportDeclaration")
8082        };
8083        // The declaration-level kind is `value` (only the specifiers are typed).
8084        assert_eq!(gc.ctx().atom_table.bytes(d.import_kind.get()), b"value");
8085        let kinds: Vec<Vec<u8>> = d
8086            .specifiers
8087            .iter()
8088            .map(|s| {
8089                let Node::ImportSpecifier(is) = s else {
8090                    panic!("expected ImportSpecifier")
8091                };
8092                gc.ctx().atom_table.bytes(is.import_kind.get()).to_vec()
8093            })
8094            .collect();
8095        assert_eq!(kinds, vec![b"type".to_vec(), b"typeof".to_vec()]);
8096    }
8097
8098    /// The `import type from 'x'` trap: a default import literally named `type`
8099    /// (not a type import). The declaration kind stays `value` and the single
8100    /// specifier is an ImportDefaultSpecifier named `type`.
8101    #[test]
8102    fn flow_import_type_from_trap() {
8103        use hermes_ast::context::Context;
8104        use hermes_ast::node::Node;
8105        use hermes_support::manager::SourceErrorManager;
8106
8107        let mut sm = SourceErrorManager::new();
8108        let mut ctx = Context::new();
8109        ctx.set_parse_flow(true);
8110        let gc = ctx.lock();
8111        let stmt = flow_parse_stmt_at(&gc, &mut sm, b"import type from 'x';", 0);
8112        let Node::ImportDeclaration(d) = stmt else {
8113            panic!("expected ImportDeclaration")
8114        };
8115        assert_eq!(
8116            gc.ctx().atom_table.bytes(d.import_kind.get()),
8117            b"value",
8118            "the trap resets the kind to value"
8119        );
8120        let spec = d.specifiers.iter().next().expect("one specifier");
8121        let Node::ImportDefaultSpecifier(s) = spec else {
8122            panic!("expected ImportDefaultSpecifier, got {:?}", spec.kind())
8123        };
8124        let Node::Identifier(local) = s.local else {
8125            panic!("expected Identifier local")
8126        };
8127        assert_eq!(gc.ctx().atom_table.bytes(local.name.get()), b"type");
8128    }
8129
8130    /// A `declare module`'s body recurses into the `declare` statement branch,
8131    /// so an inner `declare export var` parses.
8132    #[test]
8133    fn flow_declare_module_body_recursion() {
8134        use hermes_ast::context::Context;
8135        use hermes_ast::node::Node;
8136        use hermes_support::manager::SourceErrorManager;
8137
8138        let mut sm = SourceErrorManager::new();
8139        let mut ctx = Context::new();
8140        ctx.set_parse_flow(true);
8141        let gc = ctx.lock();
8142        let stmt = flow_parse_stmt_at(
8143            &gc,
8144            &mut sm,
8145            b"declare module 'x' { declare export var y: number; }",
8146            0,
8147        );
8148        let Node::DeclareModule(m) = stmt else {
8149            panic!("expected DeclareModule")
8150        };
8151        let Node::BlockStatement(b) = m.body else {
8152            panic!("expected BlockStatement body")
8153        };
8154        let inner = b.body.iter().next().expect("one inner declaration");
8155        assert!(
8156            matches!(inner, Node::DeclareExportDeclaration(_)),
8157            "inner declare export var parses, got {:?}",
8158            inner.kind()
8159        );
8160    }
8161
8162    /// `declare component`/`declare hook` route through the component-syntax
8163    /// parsers (gated on the dedicated flag).
8164    #[test]
8165    fn flow_declare_component_and_hook() {
8166        use hermes_ast::context::Context;
8167        use hermes_ast::node::Node;
8168        use hermes_support::manager::SourceErrorManager;
8169
8170        let mut sm = SourceErrorManager::new();
8171        let mut ctx = Context::new();
8172        ctx.set_parse_flow(true);
8173        ctx.set_parse_flow_component_syntax(true);
8174        let gc = ctx.lock();
8175        let body = flow_parse_body(
8176            &gc,
8177            &mut sm,
8178            b"declare component Foo(p: number) renders Bar;\n\
8179              declare hook useY(a: string): number;",
8180            true,
8181        );
8182        assert!(matches!(body[0], Node::DeclareComponent(_)));
8183        assert!(matches!(body[1], Node::DeclareHook(_)));
8184    }
8185
8186    /// Plain `import`/`export` are unaffected by the kind-erasure revert:
8187    /// the declaration kind stays `value` and a plain import/export parses.
8188    #[test]
8189    fn flow_plain_import_export_unaffected() {
8190        use hermes_ast::context::Context;
8191        use hermes_ast::node::Node;
8192        use hermes_support::manager::SourceErrorManager;
8193
8194        // Plain JS (no Flow): `import {a as b} from 'm';` parses with kind value.
8195        let mut sm = SourceErrorManager::new();
8196        let mut ctx = Context::new();
8197        let gc = ctx.lock();
8198        let stmt =
8199            flow_parse_stmt_at(&gc, &mut sm, b"import {a as b} from 'm';", 0);
8200        let Node::ImportDeclaration(d) = stmt else {
8201            panic!("expected ImportDeclaration")
8202        };
8203        assert_eq!(gc.ctx().atom_table.bytes(d.import_kind.get()), b"value");
8204        let Node::ImportSpecifier(is) =
8205            d.specifiers.iter().next().unwrap()
8206        else {
8207            panic!("expected ImportSpecifier")
8208        };
8209        assert_eq!(gc.ctx().atom_table.bytes(is.import_kind.get()), b"value");
8210
8211        // Plain export.
8212        let stmt2 =
8213            flow_parse_stmt_at(&gc, &mut sm, b"export {a as b};", 0);
8214        assert!(matches!(stmt2, Node::ExportNamedDeclaration(_)));
8215    }
8216}