Skip to main content

oxc_parser/
lib.rs

1//! Oxc Parser for JavaScript and TypeScript
2//!
3//! Oxc's [`Parser`] has full support for
4//! - The latest stable ECMAScript syntax
5//! - TypeScript
6//! - JSX and TSX
7//! - [Stage 3 Decorators](https://github.com/tc39/proposal-decorator-metadata)
8//!
9//! # Usage
10//!
11//! The parser has a minimal API with three inputs (a [memory arena](oxc_allocator::Allocator), a
12//! source string, and a [`SourceType`]) and one return struct (a [ParserReturn]).
13//!
14//! ```rust,ignore
15//! let parser_return = Parser::new(&allocator, &source_text, source_type).parse();
16//! ```
17//!
18//! # Abstract Syntax Tree (AST)
19//! Oxc's AST is located in a separate [`oxc_ast`] crate. You can find type definitions for AST
20//! nodes [here][`oxc_ast::ast`].
21//!
22//! # Performance
23//!
24//! The following optimization techniques are used:
25//! * AST is allocated in a memory arena ([oxc_allocator](https://docs.rs/oxc_allocator)) for fast AST drop
26//! * [`oxc_span::Span`] offsets uses `u32` instead of `usize`
27//! * Scope binding, symbol resolution and complicated syntax errors are not done in the parser,
28//! they are delegated to the [semantic analyzer](https://docs.rs/oxc_semantic)
29//!
30//! <div class="warning">
31//! Because [`oxc_span::Span`] uses `u32` instead of `usize`, Oxc can only parse files up
32//! to 4 GiB in size. This shouldn't be a limitation in almost all cases.
33//! </div>
34//!
35//! # Examples
36//!
37//! <https://github.com/oxc-project/oxc/blob/main/crates/oxc_parser/examples/parser.rs>
38//!
39//! ```rust,ignore
40#![doc = include_str!("../examples/parser.rs")]
41//! ```
42//!
43//! ### Parsing TSX
44//! ```rust,ignore
45#![doc = include_str!("../examples/parser_tsx.rs")]
46//! ```
47//!
48//! # Visitor
49//!
50//! See [`Visit`](http://docs.rs/oxc_ast_visit) and [`VisitMut`](http://docs.rs/oxc_ast_visit).
51//!
52//! # Visiting without a visitor
53//!
54//! For ad-hoc tasks, the semantic analyzer can be used to get a parent pointing tree with untyped nodes,
55//! the nodes can be iterated through a sequential loop.
56//!
57//! ```rust,ignore
58//! for node in semantic.nodes().iter() {
59//!     match node.kind() {
60//!         // check node
61//!     }
62//! }
63//! ```
64//!
65//! See [full linter example](https://github.com/Boshen/oxc/blob/ab2ef4f89ba3ca50c68abb2ca43e36b7793f3673/crates/oxc_linter/examples/linter.rs#L38-L39)
66
67use std::any::Any;
68
69mod ast_builder;
70pub mod config;
71mod context;
72mod cursor;
73mod error_handler;
74mod modifiers;
75mod module_record;
76mod state;
77
78mod js;
79mod jsx;
80mod ts;
81
82mod diagnostics;
83
84// Expose lexer only in benchmarks
85#[cfg(not(feature = "benchmarking"))]
86mod lexer;
87#[cfg(feature = "benchmarking")]
88#[doc(hidden)]
89pub mod lexer;
90
91use oxc_allocator::{Allocator, ArenaBox, ArenaVec, Dummy, GetAllocator};
92use oxc_ast::{
93    ast::{Expression, Program, Statement},
94    builder::{AstBuilder, GetAstBuilder},
95};
96use oxc_diagnostics::Diagnostics;
97use oxc_span::{SourceType, Span};
98use oxc_syntax::module_record::ModuleRecord;
99
100pub use crate::lexer::{Kind, Token};
101use crate::{
102    ast_builder::ParserAstBuilder,
103    config::{
104        LexerConfig, NoTokensParserConfig, ParserConfig, RuntimeParserConfig, TokensParserConfig,
105    },
106    context::{Context, StatementContext},
107    diagnostics::ParserDiagnostic,
108    error_handler::FatalError,
109    lexer::Lexer,
110    module_record::ModuleRecordBuilder,
111    state::ParserState,
112};
113
114/// Maximum length of source which can be parsed (in bytes).
115/// ~4 GiB on 64-bit systems, ~2 GiB on 32-bit systems.
116//
117// Length is constrained by 2 factors:
118// 1. `Span`'s `start` and `end` are `u32`s, which limits length to `u32::MAX` bytes.
119// 2. Rust's allocator APIs limit allocations to `isize::MAX`.
120//    https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align
121//
122// On 64-bit systems, the limit is 256 bytes below `u32::MAX` rather than `u32::MAX` itself:
123//
124// 1. `oxc_lexer` will require source text to be followed by 64 bytes of padding.
125//    This means *padded* length can fit in a `u32`.
126//    256 instead of 64 to leave headroom, in case the padding requirement expands in future.
127// 2. Counts of tokens, comments and errors are all bounded by source length plus a small constant,
128//    so they can be stored as `u32`s without any possibility of overflow.
129// 3. No real `Span` can have `start` or `end` of `u32::MAX`, so such a `Span` can be used as a sentinel.
130//    `oxc_transformer`'s styled-components plugin relies on this.
131pub(crate) const MAX_LEN: usize = if size_of::<usize>() >= 8 {
132    // 64-bit systems
133    u32::MAX as usize - 256
134} else {
135    // 32-bit or 16-bit systems
136    isize::MAX as usize
137};
138
139/// Return value of [`Parser::parse`] consisting of AST, errors and comments
140///
141/// ## AST Validity
142///
143/// [`program`] will always contain a structurally valid AST, even if there are syntax errors.
144/// However, the AST may be semantically invalid. To ensure a valid AST,
145/// 1. Check that [`diagnostics`] is empty
146/// 2. Run semantic analysis with [syntax error checking
147///    enabled](https://docs.rs/oxc_semantic/latest/oxc_semantic/struct.SemanticBuilder.html#method.with_check_syntax_error)
148///
149/// ## Errors
150///
151/// Oxc's [`Parser`] is able to recover from some syntax errors and continue parsing.
152/// When this happens:
153///
154/// 1. [`diagnostics`] will be non-empty
155/// 2. [`program`] will contain a full AST
156/// 3. [`fatal_error`] will be false
157///
158/// When the parser cannot recover, it will abort and terminate parsing early.
159/// [`program`] will be empty and [`fatal_error`] will be `true`.
160///
161/// [`program`]: ParserReturn::program
162/// [`diagnostics`]: ParserReturn::diagnostics
163/// [`fatal_error`]: ParserReturn::fatal_error
164#[non_exhaustive]
165pub struct ParserReturn<'a> {
166    /// The parsed AST.
167    ///
168    /// Will be empty (e.g. no statements, directives, etc) if the parser encountered a fatal error.
169    ///
170    /// Every node has a unique node ID within this AST. Program has ID zero;
171    /// other IDs follow construction order and may have gaps from discarded nodes.
172    /// Semantic analysis replaces these with its own dense node IDs.
173    ///
174    /// ## Validity
175    /// It is possible for the AST to be present and semantically invalid. This will happen if
176    /// 1. The [`Parser`] encounters a recoverable syntax error
177    /// 2. The logic for checking the violation is in the semantic analyzer
178    ///
179    /// To ensure a valid AST, check that [`diagnostics`](ParserReturn::diagnostics) is empty. Then, run
180    /// semantic analysis with syntax error checking enabled.
181    pub program: Program<'a>,
182
183    /// See <https://tc39.es/ecma262/#sec-abstract-module-records>
184    pub module_record: ModuleRecord<'a>,
185
186    /// Syntax errors encountered while parsing.
187    ///
188    /// This list is not comprehensive. Oxc offloads more-expensive checks to [semantic
189    /// analysis](https://docs.rs/oxc_semantic), which can be enabled using
190    /// [`SemanticBuilder::with_check_syntax_error`](https://docs.rs/oxc_semantic/latest/oxc_semantic/struct.SemanticBuilder.html#method.with_check_syntax_error).
191    pub diagnostics: Diagnostics,
192
193    /// Irregular whitespaces for `Oxlint`
194    pub irregular_whitespaces: Box<[Span]>,
195
196    /// Lexed tokens in source order.
197    ///
198    /// Tokens are only collected when tokens are enabled in [`ParserConfig`].
199    pub tokens: ArenaVec<'a, Token>,
200
201    /// Whether the parser encountered a fatal error and terminated early.
202    ///
203    /// If `false`, either parsing was successful, or parsing was able to recover from a syntax error.
204    /// [`diagnostics`] may still contain errors, indicating that the program is not syntactically valid,
205    /// but parser was able to recover enough to produce an AST.
206    ///
207    /// If `true`, parser was unable to parse the source.
208    /// [`program`] will be empty and [`diagnostics`] will contain at least one error.
209    ///
210    /// [`program`]: ParserReturn::program
211    /// [`diagnostics`]: ParserReturn::diagnostics
212    pub fatal_error: bool,
213
214    /// Whether the file is [flow](https://flow.org).
215    pub is_flow_language: bool,
216}
217
218/// Parse options
219///
220/// You may provide options to the [`Parser`] using [`Parser::with_options`].
221#[derive(Debug, Clone, Copy)]
222pub struct ParseOptions {
223    /// Whether to parse regular expressions or not.
224    ///
225    /// Default: `false`
226    #[cfg(feature = "regular_expression")]
227    pub parse_regular_expression: bool,
228
229    /// Allow [`return`] statements outside of functions.
230    ///
231    /// By default, a return statement at the top level raises an error (`false`).
232    ///
233    /// Default: `false`
234    ///
235    /// [`return`]: oxc_ast::ast::ReturnStatement
236    pub allow_return_outside_function: bool,
237
238    /// Emit [`ParenthesizedExpression`]s and [`TSParenthesizedType`] in AST.
239    ///
240    /// If this option is `true`, parenthesized expressions are represented by
241    /// (non-standard) [`ParenthesizedExpression`] and [`TSParenthesizedType`] nodes
242    /// that have a single `expression` property containing the expression inside parentheses.
243    ///
244    /// Default: `true`
245    ///
246    /// [`ParenthesizedExpression`]: oxc_ast::ast::ParenthesizedExpression
247    /// [`TSParenthesizedType`]: oxc_ast::ast::TSParenthesizedType
248    pub preserve_parens: bool,
249
250    /// Allow V8 runtime calls in the AST.
251    /// See: [V8's Parser::ParseV8Intrinsic](https://chromium.googlesource.com/v8/v8/+/35a14c75e397302655d7b3fbe648f9490ae84b7d/src/parsing/parser.cc#4811).
252    ///
253    /// Default: `false`
254    ///
255    /// [`V8IntrinsicExpression`]: oxc_ast::ast::V8IntrinsicExpression
256    pub allow_v8_intrinsics: bool,
257
258    /// Precompute hashes for identifier names (`Ident`s) in the AST.
259    ///
260    /// Precomputed hashes speed up `Ident`-keyed hash map operations in semantic analysis
261    /// and later compilation stages, at a small cost to parsing speed.
262    ///
263    /// Only disable this for parse-only pipelines that never rely on `Ident` hashing, such as
264    /// parse + serialize or formatting. Unhashed `Ident`s do not compare equal to hashed ones,
265    /// so semantic analysis and anything else relying on `Ident` hashing must not run on an AST
266    /// parsed with this option disabled.
267    ///
268    /// Default: `true`
269    pub enable_ident_hashes: bool,
270}
271
272impl Default for ParseOptions {
273    fn default() -> Self {
274        Self {
275            #[cfg(feature = "regular_expression")]
276            parse_regular_expression: false,
277            allow_return_outside_function: false,
278            preserve_parens: true,
279            allow_v8_intrinsics: false,
280            enable_ident_hashes: true,
281        }
282    }
283}
284
285/// Recursive Descent Parser for ECMAScript and TypeScript
286///
287/// See [`Parser::parse`] for entry function.
288pub struct Parser<'a, C: ParserConfig = NoTokensParserConfig> {
289    allocator: &'a Allocator,
290    source_text: &'a str,
291    source_type: SourceType,
292    options: ParseOptions,
293    config: C,
294}
295
296impl<'a> Parser<'a> {
297    /// Create a new [`Parser`]
298    ///
299    /// # Parameters
300    /// - `allocator`: [Memory arena](oxc_allocator::Allocator) for allocating AST nodes
301    /// - `source_text`: Source code to parse
302    /// - `source_type`: Source type (e.g. JavaScript, TypeScript, JSX, ESM Module, Script)
303    pub fn new(allocator: &'a Allocator, source_text: &'a str, source_type: SourceType) -> Self {
304        let options = ParseOptions::default();
305        Self { allocator, source_text, source_type, options, config: NoTokensParserConfig }
306    }
307}
308
309impl<'a, C: ParserConfig> Parser<'a, C> {
310    /// Set parse options
311    #[must_use]
312    pub fn with_options(mut self, options: ParseOptions) -> Self {
313        self.options = options;
314        self
315    }
316
317    /// Set parser config.
318    ///
319    /// See [`ParserConfig`] for more details.
320    #[must_use]
321    pub fn with_config<Config: ParserConfig>(self, config: Config) -> Parser<'a, Config> {
322        Parser {
323            allocator: self.allocator,
324            source_text: self.source_text,
325            source_type: self.source_type,
326            options: self.options,
327            config,
328        }
329    }
330}
331
332mod parser_parse {
333    use super::*;
334
335    /// `UniquePromise` is a way to use the type system to enforce the invariant that only
336    /// a single `ParserImpl`, `Lexer` and `lexer::Source` can exist at any time on a thread.
337    /// This constraint is required to guarantee the soundness of some methods of these types
338    /// e.g. `Source::set_position`.
339    ///
340    /// `ParserImpl::new`, `Lexer::new` and `lexer::Source::new` all require a `UniquePromise`
341    /// to be provided to them. `UniquePromise::new` is not visible outside this module, so only
342    /// `Parser::parse` can create one, and it only calls `ParserImpl::new` once.
343    /// This enforces the invariant throughout the entire parser.
344    ///
345    /// `UniquePromise` is a zero-sized type and has no runtime cost. It's purely for the type-checker.
346    ///
347    /// `UniquePromise::new_for_tests_and_benchmarks` is a backdoor for tests/benchmarks, so they can
348    /// create a `ParserImpl` or `Lexer`, and manipulate it directly, for testing/benchmarking purposes.
349    pub struct UniquePromise(());
350
351    impl UniquePromise {
352        #[inline]
353        fn new() -> Self {
354            Self(())
355        }
356
357        /// Backdoor for tests/benchmarks to create a `UniquePromise` (see above).
358        /// This function must NOT be exposed outside of tests and benchmarks,
359        /// as it allows circumventing safety invariants of the parser.
360        #[cfg(any(test, feature = "benchmarking"))]
361        pub fn new_for_tests_and_benchmarks() -> Self {
362            Self(())
363        }
364    }
365
366    impl<'a, C: ParserConfig> Parser<'a, C> {
367        /// Main entry point
368        ///
369        /// Returns an empty `Program` on unrecoverable error,
370        /// Recoverable errors are stored inside `errors`.
371        ///
372        /// See the [module-level documentation](crate) for examples and more information.
373        //
374        // # Implementation note
375        //
376        // Dispatches via `Any` to a non-generic helper for each known `ParserConfig`.
377        // The dispatch keeps the parser body emitted exactly once in `oxc_parser`'s rlib,
378        // so consuming crates don't each get a private copy. The `Any::is` / `downcast_ref`
379        // calls const-fold when `C` is concrete (the trait object is built from a known
380        // concrete type at every monomorphization site, so LLVM devirtualises the vtable
381        // call to `Any::type_id` and folds the comparison), leaving each monomorphization
382        // as a single call into the matching helper.
383        pub fn parse(self) -> ParserReturn<'a> {
384            let config: &dyn Any = &self.config;
385            if config.is::<NoTokensParserConfig>() {
386                parse_with_no_tokens_config(
387                    self.allocator,
388                    self.source_text,
389                    self.source_type,
390                    self.options,
391                )
392            } else if config.is::<TokensParserConfig>() {
393                parse_with_tokens_config(
394                    self.allocator,
395                    self.source_text,
396                    self.source_type,
397                    self.options,
398                )
399            } else if let Some(&config) = config.downcast_ref::<RuntimeParserConfig>() {
400                parse_with_runtime_config(
401                    self.allocator,
402                    self.source_text,
403                    self.source_type,
404                    self.options,
405                    config,
406                )
407            } else {
408                // User-defined `ParserConfig`. Generic codegen here, monomorphized per consuming crate.
409                // Users using custom configs would need to perform the monomorphization themselves.
410                ParserImpl::<C>::new(
411                    self.allocator,
412                    self.source_text,
413                    self.source_type,
414                    self.options,
415                    self.config,
416                    UniquePromise::new(),
417                )
418                .parse()
419            }
420        }
421
422        /// Parse a single [`Expression`].
423        ///
424        /// Every node receives a unique, nonzero node ID. IDs follow construction
425        /// order and may have gaps from discarded nodes.
426        ///
427        /// # Example
428        ///
429        /// ```rust
430        /// use oxc_allocator::Allocator;
431        /// use oxc_ast::ast::Expression;
432        /// use oxc_parser::Parser;
433        /// use oxc_span::SourceType;
434        ///
435        /// let src = "1 + 2";
436        /// let allocator = Allocator::new();
437        /// let source_type = SourceType::default();
438        ///
439        /// let expr: Expression<'_> = Parser::new(&allocator, src, source_type).parse_expression().unwrap();
440        /// ```
441        ///
442        /// # Errors
443        /// If the source code being parsed has syntax errors.
444        //
445        // # Implementation note
446        // Dispatches via `Any`, same as `parse` does.
447        pub fn parse_expression(self) -> Result<Expression<'a>, Diagnostics> {
448            let config: &dyn Any = &self.config;
449            if config.is::<NoTokensParserConfig>() {
450                parse_expression_with_no_tokens_config(
451                    self.allocator,
452                    self.source_text,
453                    self.source_type,
454                    self.options,
455                )
456            } else if config.is::<TokensParserConfig>() {
457                parse_expression_with_tokens_config(
458                    self.allocator,
459                    self.source_text,
460                    self.source_type,
461                    self.options,
462                )
463            } else if let Some(&config) = config.downcast_ref::<RuntimeParserConfig>() {
464                parse_expression_with_runtime_config(
465                    self.allocator,
466                    self.source_text,
467                    self.source_type,
468                    self.options,
469                    config,
470                )
471            } else {
472                ParserImpl::<C>::new(
473                    self.allocator,
474                    self.source_text,
475                    self.source_type,
476                    self.options,
477                    self.config,
478                    UniquePromise::new(),
479                )
480                .parse_expression()
481            }
482        }
483    }
484
485    // ===========================================================================
486    // Non-generic parse helpers, one per known `ParserConfig`.
487    //
488    // The parser is generic over `C: ParserConfig`. By default Rust monomorphizes
489    // generic functions per consuming crate (the legacy mangled name encodes the
490    // instantiating crate's disambiguator, and `share-generics` is off at
491    // `opt-level >= 2`). For a parser that's pulled in by ~15 crates in a real
492    // workspace, that means ~15 private copies of every parser method in the
493    // final cdylib — none of which can be deduped by COMDAT (different names)
494    // or fat LTO (slightly different inlining contexts).
495    //
496    // To avoid that, `Parser<C>::parse` and `Parser<C>::parse_expression` dispatch
497    // via `Any` to one of the helpers below for the three known configs.
498    // Each helper is non-generic, so it's emitted exactly once in `oxc_parser`'s
499    // rlib and shared by all consumers. The `Any::is` / `downcast_ref` checks fold
500    // at compile time when `C` is concrete, so each monomorphization of the dispatch
501    // shrinks to a single call into the matching helper.
502    //
503    // The helpers are `#[inline(never)]` to prevent fat LTO from re-inlining the
504    // parser body across the rlib boundary, which would defeat the purpose.
505    //
506    // For user-defined `ParserConfig` impls (rare), the dispatch falls through
507    // to a generic body that monomorphizes per consuming crate. That's the same
508    // cost the parser had without this pattern; we just keep that cost contained
509    // to the rare custom-config case.
510    // ===========================================================================
511
512    #[inline(never)]
513    fn parse_with_no_tokens_config<'a>(
514        allocator: &'a Allocator,
515        source_text: &'a str,
516        source_type: SourceType,
517        options: ParseOptions,
518    ) -> ParserReturn<'a> {
519        ParserImpl::<NoTokensParserConfig>::new(
520            allocator,
521            source_text,
522            source_type,
523            options,
524            NoTokensParserConfig,
525            UniquePromise::new(),
526        )
527        .parse()
528    }
529
530    #[inline(never)]
531    fn parse_with_tokens_config<'a>(
532        allocator: &'a Allocator,
533        source_text: &'a str,
534        source_type: SourceType,
535        options: ParseOptions,
536    ) -> ParserReturn<'a> {
537        ParserImpl::<TokensParserConfig>::new(
538            allocator,
539            source_text,
540            source_type,
541            options,
542            TokensParserConfig,
543            UniquePromise::new(),
544        )
545        .parse()
546    }
547
548    #[inline(never)]
549    fn parse_with_runtime_config<'a>(
550        allocator: &'a Allocator,
551        source_text: &'a str,
552        source_type: SourceType,
553        options: ParseOptions,
554        config: RuntimeParserConfig,
555    ) -> ParserReturn<'a> {
556        ParserImpl::<RuntimeParserConfig>::new(
557            allocator,
558            source_text,
559            source_type,
560            options,
561            config,
562            UniquePromise::new(),
563        )
564        .parse()
565    }
566
567    #[inline(never)]
568    fn parse_expression_with_no_tokens_config<'a>(
569        allocator: &'a Allocator,
570        source_text: &'a str,
571        source_type: SourceType,
572        options: ParseOptions,
573    ) -> Result<Expression<'a>, Diagnostics> {
574        ParserImpl::<NoTokensParserConfig>::new(
575            allocator,
576            source_text,
577            source_type,
578            options,
579            NoTokensParserConfig,
580            UniquePromise::new(),
581        )
582        .parse_expression()
583    }
584
585    #[inline(never)]
586    fn parse_expression_with_tokens_config<'a>(
587        allocator: &'a Allocator,
588        source_text: &'a str,
589        source_type: SourceType,
590        options: ParseOptions,
591    ) -> Result<Expression<'a>, Diagnostics> {
592        ParserImpl::<TokensParserConfig>::new(
593            allocator,
594            source_text,
595            source_type,
596            options,
597            TokensParserConfig,
598            UniquePromise::new(),
599        )
600        .parse_expression()
601    }
602
603    #[inline(never)]
604    fn parse_expression_with_runtime_config<'a>(
605        allocator: &'a Allocator,
606        source_text: &'a str,
607        source_type: SourceType,
608        options: ParseOptions,
609        config: RuntimeParserConfig,
610    ) -> Result<Expression<'a>, Diagnostics> {
611        ParserImpl::<RuntimeParserConfig>::new(
612            allocator,
613            source_text,
614            source_type,
615            options,
616            config,
617            UniquePromise::new(),
618        )
619        .parse_expression()
620    }
621}
622use parser_parse::UniquePromise;
623
624/// Implementation of parser.
625/// `Parser` is just a public wrapper, the guts of the implementation is in this type.
626struct ParserImpl<'a, C: ParserConfig> {
627    /// Options
628    options: ParseOptions,
629
630    pub(crate) lexer: Lexer<'a, C::LexerConfig>,
631
632    /// SourceType: JavaScript or TypeScript, Script or Module, jsx support?
633    source_type: SourceType,
634
635    /// Source Code
636    source_text: &'a str,
637
638    /// All syntax errors from parser and lexer
639    /// Note: favor adding to `Diagnostics` instead of raising Err
640    ///
641    /// Stored in deferred (unmaterialized) form and materialized once at parse exit.
642    errors: Vec<ParserDiagnostic<'a>>,
643
644    /// Errors that are only valid if the file is determined to be a Script (not a Module).
645    /// For `ModuleKind::Unambiguous`, we defer ESM-only errors (like top-level await)
646    /// until we know whether the file is ESM or Script.
647    /// If resolved to Module → discard these errors.
648    /// If resolved to Script → emit these errors.
649    deferred_script_errors: Vec<ParserDiagnostic<'a>>,
650
651    fatal_error: Option<FatalError<'a>>,
652
653    /// The current parsing token
654    token: Token,
655
656    /// The end range of the previous token
657    prev_token_end: u32,
658
659    /// Parser state
660    state: ParserState<'a>,
661
662    /// Parsing context
663    ctx: Context,
664
665    /// Ast builder for creating AST nodes
666    ast: ParserAstBuilder<'a>,
667
668    /// Module Record Builder
669    module_record_builder: ModuleRecordBuilder<'a>,
670
671    /// Precomputed typescript detection
672    is_ts: bool,
673}
674
675impl<'a, C: ParserConfig> ParserImpl<'a, C> {
676    /// Create a new `ParserImpl`.
677    ///
678    /// Requiring a `UniquePromise` to be provided guarantees only 1 `ParserImpl` can exist
679    /// on a single thread at one time.
680    #[inline]
681    #[expect(clippy::needless_pass_by_value)]
682    pub fn new(
683        allocator: &'a Allocator,
684        source_text: &'a str,
685        source_type: SourceType,
686        options: ParseOptions,
687        config: C,
688        unique: UniquePromise,
689    ) -> Self {
690        Self {
691            options,
692            lexer: Lexer::new(allocator, source_text, source_type, config.lexer_config(), unique),
693            source_type,
694            source_text,
695            errors: vec![],
696            deferred_script_errors: vec![],
697            fatal_error: None,
698            token: Token::default(),
699            prev_token_end: 0,
700            state: ParserState::new(),
701            ctx: Self::default_context(source_type, options),
702            ast: ParserAstBuilder::new(allocator),
703            module_record_builder: ModuleRecordBuilder::new(allocator, source_type),
704            is_ts: source_type.is_typescript(),
705        }
706    }
707
708    /// Main entry point
709    ///
710    /// Returns an empty `Program` on unrecoverable error,
711    /// Recoverable errors are stored inside `errors`.
712    #[inline]
713    pub fn parse(mut self) -> ParserReturn<'a> {
714        let mut program = self.parse_program();
715        let mut has_fatal_error = false;
716
717        if let Some(fatal_error) = self.fatal_error.take() {
718            has_fatal_error = true;
719            self.errors.truncate(fatal_error.errors_len);
720            if !self.lexer.errors.is_empty() && self.cur_kind().is_eof() {
721                // Noop
722            } else {
723                self.error(fatal_error.error);
724            }
725
726            program = Program::dummy(self.allocator());
727            program.source_type = self.source_type;
728            program.source_text = self.source_text;
729        }
730
731        self.check_unfinished_errors();
732
733        if let Some(overlong_error) = self.overlong_error() {
734            has_fatal_error = true;
735            self.lexer.errors.clear();
736            self.errors.clear();
737            self.error(overlong_error);
738        }
739
740        let mut is_flow_language = false;
741        let mut errors = Diagnostics::new();
742        // only check for `@flow` if the file failed to parse.
743        if (!self.lexer.errors.is_empty() || !self.errors.is_empty())
744            && let Some(error) = self.flow_error()
745        {
746            is_flow_language = true;
747            errors.push(error.into_diagnostic());
748        }
749        let (module_record, mut module_record_errors) = self.module_record_builder.build();
750        if errors.len() != 1 {
751            errors
752                .reserve(self.lexer.errors.len() + self.errors.len() + module_record_errors.len());
753            errors.extend(self.lexer.errors.drain(..).map(ParserDiagnostic::into_diagnostic));
754            errors.extend(self.errors.drain(..).map(ParserDiagnostic::into_diagnostic));
755            errors.append(&mut module_record_errors);
756        }
757        let irregular_whitespaces =
758            std::mem::take(&mut self.lexer.trivia_builder.irregular_whitespaces).into_boxed_slice();
759
760        let source_type = program.source_type;
761        if source_type.is_unambiguous() {
762            if module_record.has_module_syntax {
763                // Resolved to Module - discard deferred script errors (TLA is valid in ESM)
764                // but emit deferred module errors (HTML comments are invalid in ESM)
765                program.source_type = source_type.with_module(true);
766                errors.extend(
767                    self.lexer
768                        .deferred_module_errors
769                        .drain(..)
770                        .map(ParserDiagnostic::into_diagnostic),
771                );
772            } else {
773                // Resolved to Script - emit deferred script errors
774                // discard deferred module errors (HTML comments are valid in scripts)
775                program.source_type = source_type.with_script(true);
776                errors.extend(
777                    self.deferred_script_errors.into_iter().map(ParserDiagnostic::into_diagnostic),
778                );
779            }
780        }
781
782        let tokens = if has_fatal_error {
783            ArenaVec::new_in(&self.ast)
784        } else {
785            self.lexer.finalize_tokens()
786        };
787
788        program.comments = self.lexer.trivia_builder.comments;
789
790        ParserReturn {
791            program,
792            module_record,
793            diagnostics: errors,
794            irregular_whitespaces,
795            tokens,
796            fatal_error: has_fatal_error,
797            is_flow_language,
798        }
799    }
800
801    pub fn parse_expression(mut self) -> Result<Expression<'a>, Diagnostics> {
802        // initialize cur_token and prev_token by moving onto the first token
803        self.bump_any();
804        let expr = self.parse_expr();
805        if !self.at(Kind::Eof) {
806            self.set_unexpected();
807        }
808        if let Some(FatalError { error, .. }) = self.fatal_error.take() {
809            return Err(error.into_diagnostic().into());
810        }
811        self.check_unfinished_errors();
812        let errors = self
813            .lexer
814            .errors
815            .into_iter()
816            .chain(self.errors)
817            .map(ParserDiagnostic::into_diagnostic)
818            .collect::<Diagnostics>();
819        if !errors.is_empty() {
820            return Err(errors);
821        }
822        Ok(expr)
823    }
824
825    #[expect(clippy::cast_possible_truncation)]
826    fn parse_program(&mut self) -> Program<'a> {
827        // Initialize by moving onto the first token.
828        // Checks for hashbang comment.
829        self.token = self.lexer.first_token();
830
831        let hashbang = self.parse_hashbang();
832        self.ctx |= Context::TopLevel;
833        let (directives, mut statements) =
834            self.parse_directives_and_statements(/* in_ts_namespace_body */ false);
835
836        // In unambiguous mode, if ESM syntax was detected (import/export/import.meta),
837        // we need to reparse statements that were originally parsed with `await` as identifier.
838        // TypeScript's behavior: initially parse `await /x/` as division, then reparse as
839        // await expression with regex when ESM is detected.
840        // Preserve a fatal error from the initial parse instead of rewinding past it.
841        if self.fatal_error.is_none()
842            && self.source_type.is_unambiguous()
843            && self.module_record_builder.has_module_syntax()
844            && !self.state.potential_await_reparse.is_empty()
845        {
846            self.reparse_potential_top_level_awaits(&mut statements);
847        }
848
849        let span = Span::new(0, self.source_text.len() as u32);
850        Program::new(
851            span,
852            self.source_type,
853            self.source_text,
854            // Populated at the end of `parse` after `flow_error` has read from `trivia_builder.comments`
855            [],
856            hashbang,
857            directives,
858            statements,
859            // The default builder assigns ID zero (NodeId::ROOT) to Program.
860            // ParserAstBuilder starts at one, keeping every child ID distinct.
861            &AstBuilder::new(self.allocator()),
862        )
863    }
864
865    /// Reparse statements that may contain top-level await expressions.
866    ///
867    /// In unambiguous mode, statements like `await /x/u` are initially parsed as
868    /// `await / x / u` (identifier with divisions). If ESM syntax is detected,
869    /// we need to reparse them with the await context enabled.
870    fn reparse_potential_top_level_awaits(&mut self, statements: &mut ArenaVec<'a, Statement<'a>>) {
871        let original_tokens =
872            if self.lexer.config.tokens() { Some(self.lexer.take_tokens()) } else { None };
873        let original = original_tokens.as_ref().map_or(&[][..], |tokens| tokens.as_slice());
874        let mut original_index = 0;
875        let mut replacements = ArenaVec::new_in(self);
876
877        let checkpoints = std::mem::take(&mut self.state.potential_await_reparse);
878        // Ranges refer to the original tokens and to the flat replacement buffer
879        let mut edits = ArenaVec::with_capacity_in(
880            if self.lexer.config.tokens() { checkpoints.len() } else { 0 },
881            self,
882        );
883        for (stmt_index, checkpoint) in checkpoints {
884            self.rewind(checkpoint);
885            let replacement_start = replacements.len();
886
887            if self.lexer.config.tokens() {
888                original_index += original[original_index..]
889                    .iter()
890                    .position(|token| token.start() >= self.token.start())
891                    .unwrap_or(original.len() - original_index);
892                // The checkpoint's current token has already been lexed
893                replacements.push(self.token);
894                self.lexer.set_tokens(std::mem::replace(&mut replacements, ArenaVec::new_in(self)));
895            }
896            let edit_start = original_index;
897
898            // Parse the statement with await context enabled (TopLevel context is already set)
899            let stmt = self.context_add(Context::Await, |p| {
900                p.parse_statement_list_item(StatementContext::Program)
901            });
902
903            // Replace the statement if the index is valid
904            if stmt_index < statements.len() {
905                statements[stmt_index] = stmt;
906            }
907
908            if self.lexer.config.tokens() {
909                replacements = self.lexer.take_tokens();
910                // Exclude the next statement's token (or EOF)
911                replacements.pop();
912                // An earlier reparse can consume a later candidate, as in `await\nawait /x/u`.
913                // Record overlapping source ranges only once.
914                let end = replacements[..replacement_start].last().map_or(0, Token::end);
915                let overlap_len =
916                    replacements[replacement_start..].partition_point(|token| token.start() < end);
917                if overlap_len != 0 {
918                    replacements.copy_within(replacement_start + overlap_len.., replacement_start);
919                    let len = replacements.len() - overlap_len;
920                    replacements.truncate(len);
921                }
922                original_index += original[original_index..]
923                    .iter()
924                    .position(|token| token.start() >= self.token.start())
925                    .unwrap_or(original.len() - original_index);
926                edits.push((edit_start..original_index, replacement_start..replacements.len()));
927            }
928        }
929
930        if let Some(mut tokens) = original_tokens {
931            let mut read = 0;
932            if edits.iter().all(|(range, replacement)| replacement.len() <= range.len()) {
933                // Compact in place. Original indices remain valid because the write cursor
934                // never overtakes the read cursor.
935                let mut write = 0;
936                for (range, replacement) in edits {
937                    tokens.copy_within(read..range.start, write);
938                    write += range.start - read;
939                    let replacement = &replacements[replacement];
940                    tokens[write..write + replacement.len()].copy_from_slice(replacement);
941                    write += replacement.len();
942                    read = range.end;
943                }
944                let tail_len = tokens.len() - read;
945                tokens.copy_within(read.., write);
946                tokens.truncate(write + tail_len);
947                self.lexer.set_tokens(tokens);
948            } else {
949                // A growing replacement could overwrite unread tokens.
950                // Use a separate output buffer, still applying all edits in one forward pass.
951                let len = edits.iter().fold(tokens.len(), |len, (range, replacement)| {
952                    len - range.len() + replacement.len()
953                });
954                let mut output = ArenaVec::with_capacity_in(len, self);
955                for (range, replacement) in edits {
956                    output.extend_from_slice(&tokens[read..range.start]);
957                    output.extend_from_slice(&replacements[replacement]);
958                    read = range.end;
959                }
960                output.extend_from_slice(&tokens[read..]);
961                self.lexer.set_tokens(output);
962            }
963        }
964    }
965
966    fn default_context(source_type: SourceType, options: ParseOptions) -> Context {
967        let mut ctx = Context::default().and_ambient(source_type.is_typescript_definition());
968        if source_type.is_module() {
969            // for [top-level-await](https://tc39.es/proposal-top-level-await/)
970            ctx = ctx.and_await(true);
971        }
972        // CommonJS files are wrapped in a function, so return and `new.target`
973        // are allowed at top-level
974        if options.allow_return_outside_function || source_type.is_commonjs() {
975            ctx = ctx.and_return(true);
976        }
977        if source_type.is_commonjs() {
978            ctx = ctx.and_new_target(true);
979        }
980        ctx
981    }
982
983    /// Check for Flow declaration if the file cannot be parsed.
984    /// The declaration must be [on the first line before any code](https://flow.org/en/docs/usage/#toc-prepare-your-code-for-flow)
985    fn flow_error(&mut self) -> Option<ParserDiagnostic<'a>> {
986        if !self.source_type.is_javascript() {
987            return None;
988        }
989        let span = self.lexer.trivia_builder.comments.first()?.span;
990        if span.source_text(self.source_text).contains("@flow") {
991            self.errors.clear();
992            Some(diagnostics::flow(span))
993        } else {
994            None
995        }
996    }
997
998    fn check_unfinished_errors(&mut self) {
999        use oxc_span::GetSpan;
1000        // PropertyDefinition : cover_initialized_name
1001        // It is a Syntax Error if any source text is matched by this production.
1002        for expr in self.state.cover_initialized_name.values() {
1003            self.errors.push(diagnostics::cover_initialized_name(expr.span()));
1004        }
1005    }
1006
1007    /// Check if source length exceeds MAX_LEN, if the file cannot be parsed.
1008    /// Original parsing error is not real - `Lexer::new` substituted "\0" as the source text.
1009    #[cold]
1010    fn overlong_error(&self) -> Option<ParserDiagnostic<'a>> {
1011        if self.source_text.len() > MAX_LEN {
1012            return Some(diagnostics::overlong_source());
1013        }
1014        None
1015    }
1016
1017    #[inline]
1018    fn alloc<T>(&self, value: T) -> ArenaBox<'a, T> {
1019        ArenaBox::new_in(value, self)
1020    }
1021}
1022
1023impl<'a, C: ParserConfig> GetAllocator<'a> for ParserImpl<'a, C> {
1024    #[inline]
1025    fn allocator(&self) -> &'a Allocator {
1026        self.ast.allocator()
1027    }
1028}
1029
1030impl<'a, C: ParserConfig> GetAstBuilder<'a> for ParserImpl<'a, C> {
1031    type Builder = ParserAstBuilder<'a>;
1032
1033    #[inline]
1034    fn builder(&self) -> &ParserAstBuilder<'a> {
1035        &self.ast
1036    }
1037}
1038
1039#[cfg(test)]
1040mod test {
1041    use std::path::Path;
1042
1043    use oxc_ast::ast::{
1044        CommentKind, Expression, Statement, TSNamespaceDeclarationBody, TSNamespaceDeclarationKind,
1045    };
1046    use oxc_span::GetSpan;
1047
1048    use super::*;
1049
1050    #[test]
1051    fn parse_program_smoke_test() {
1052        let allocator = Allocator::default();
1053        let source_type = SourceType::default();
1054        let source = "";
1055        let ret = Parser::new(&allocator, source, source_type).parse();
1056        assert!(ret.program.is_empty());
1057        assert!(ret.diagnostics.is_empty());
1058        assert!(!ret.is_flow_language);
1059    }
1060
1061    #[test]
1062    fn parse_expression_smoke_test() {
1063        let allocator = Allocator::default();
1064        let source_type = SourceType::default();
1065        let source = "a";
1066        let expr = Parser::new(&allocator, source, source_type).parse_expression().unwrap();
1067        assert!(matches!(expr, Expression::Identifier(_)));
1068    }
1069
1070    #[test]
1071    fn parse_expression_rejects_trailing_tokens() {
1072        let allocator = Allocator::default();
1073        let source_type = SourceType::default();
1074        for source in ["a b", "a;", "let x = 1"] {
1075            assert!(Parser::new(&allocator, source, source_type).parse_expression().is_err());
1076        }
1077    }
1078
1079    #[test]
1080    fn flow_error() {
1081        let allocator = Allocator::default();
1082        let source_type = SourceType::default();
1083        let sources = [
1084            "// @flow\nasdf adsf",
1085            "/* @flow */\n asdf asdf",
1086            "/**
1087             * @flow
1088             */
1089             asdf asdf
1090             ",
1091            "/* @flow */ super;",
1092        ];
1093        for source in sources {
1094            let ret = Parser::new(&allocator, source, source_type).parse();
1095            assert!(ret.is_flow_language);
1096            assert_eq!(ret.diagnostics.len(), 1);
1097            assert_eq!(ret.diagnostics.first().unwrap().to_string(), "Flow is not supported");
1098        }
1099    }
1100
1101    #[test]
1102    fn ts_module_declaration() {
1103        let allocator = Allocator::default();
1104        let source_type = SourceType::from_path(Path::new("module.ts")).unwrap();
1105        let source =
1106            "declare module 'test'; namespace Foo.Bar {} module Baz {} declare module 'raw' {}";
1107        let ret = Parser::new(&allocator, source, source_type).parse();
1108        assert!(ret.diagnostics.is_empty());
1109
1110        let Statement::TSExternalModuleDeclaration(external) = &ret.program.body[0] else {
1111            panic!("expected external module declaration");
1112        };
1113        assert_eq!(external.id.value, "test");
1114        assert!(external.body.is_none());
1115        assert!(external.declare);
1116
1117        let Statement::TSNamespaceDeclaration(namespace) = &ret.program.body[1] else {
1118            panic!("expected namespace declaration");
1119        };
1120        assert_eq!(namespace.id.name, "Foo");
1121        assert_eq!(namespace.kind, TSNamespaceDeclarationKind::Namespace);
1122        let TSNamespaceDeclarationBody::TSNamespaceDeclaration(inner) = &namespace.body else {
1123            panic!("expected dotted namespace declaration");
1124        };
1125        assert_eq!(inner.id.name, "Bar");
1126
1127        let Statement::TSNamespaceDeclaration(module) = &ret.program.body[2] else {
1128            panic!("expected identifier module declaration");
1129        };
1130        assert_eq!(module.id.name, "Baz");
1131        assert_eq!(module.kind, TSNamespaceDeclarationKind::Module);
1132
1133        let Statement::TSExternalModuleDeclaration(external) = &ret.program.body[3] else {
1134            panic!("expected external module declaration");
1135        };
1136        assert_eq!(external.id.value, "raw");
1137        assert!(external.body.is_some());
1138        assert!(external.declare);
1139    }
1140
1141    #[test]
1142    fn surrogate_halves_join_across_escape_forms() {
1143        let allocator = Allocator::default();
1144        let source_type = SourceType::default();
1145        // Each spelling escapes U+1F600 as its two surrogate halves.
1146        let escapes = [
1147            r"\uD83D\uDE00",
1148            r"\u{D83D}\u{DE00}",
1149            r"\uD83D\u{DE00}",
1150            r"\u{D83D}\uDE00",
1151            // A LineContinuation contributes no code units, so the halves are adjacent.
1152            "\\uD83D\\\n\\uDE00",
1153        ];
1154        for escape in escapes {
1155            let source = format!("'{escape}'");
1156            let expr = Parser::new(&allocator, &source, source_type).parse_expression().unwrap();
1157            let Expression::StringLiteral(literal) = expr else { panic!("{source}") };
1158            assert_eq!(literal.value.as_str(), Some("😀"), "{source}");
1159
1160            let source = format!("`{escape}`");
1161            let expr = Parser::new(&allocator, &source, source_type).parse_expression().unwrap();
1162            let Expression::TemplateLiteral(template) = expr else { panic!("{source}") };
1163            let cooked = template.quasis[0].value.cooked.and_then(oxc_str::JSStr::as_str);
1164            assert_eq!(cooked, Some("😀"), "{source}");
1165        }
1166    }
1167
1168    #[test]
1169    fn directives() {
1170        let allocator = Allocator::default();
1171        let source_type = SourceType::default();
1172        let sources = [
1173            ("import x from 'foo'; 'use strict';", 2),
1174            ("export {x} from 'foo'; 'use strict';", 2),
1175            (";'use strict';", 2),
1176        ];
1177        for (source, body_length) in sources {
1178            let ret = Parser::new(&allocator, source, source_type).parse();
1179            assert!(ret.program.directives.is_empty(), "{source}");
1180            assert_eq!(ret.program.body.len(), body_length, "{source}");
1181        }
1182    }
1183
1184    #[test]
1185    fn v8_intrinsics() {
1186        let allocator = Allocator::default();
1187        let source_type = SourceType::default();
1188        {
1189            let source = "%DebugPrint('Raging against the Dying Light')";
1190            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1191            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1192            assert!(ret.diagnostics.is_empty());
1193
1194            if let Some(Statement::ExpressionStatement(expr_stmt)) = ret.program.body.first() {
1195                if let Expression::V8IntrinsicExpression(expr) = &expr_stmt.expression {
1196                    assert_eq!(expr.span().source_text(source), source);
1197                } else {
1198                    panic!("Expected V8IntrinsicExpression");
1199                }
1200            } else {
1201                panic!("Expected ExpressionStatement");
1202            }
1203        }
1204        {
1205            let source = "%DebugPrint(...illegalSpread)";
1206            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1207            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1208            assert_eq!(ret.diagnostics.len(), 1);
1209            assert_eq!(
1210                ret.diagnostics[0].to_string(),
1211                "V8 runtime calls cannot have spread elements as arguments"
1212            );
1213        }
1214        {
1215            let source = "%DebugPrint('~~')";
1216            let ret = Parser::new(&allocator, source, source_type).parse();
1217            assert_eq!(ret.diagnostics.len(), 1);
1218            assert_eq!(ret.diagnostics[0].to_string(), "Unexpected token");
1219        }
1220        {
1221            // https://github.com/oxc-project/oxc/issues/12121
1222            let source = "interface Props extends %enuProps {}";
1223            let source_type = SourceType::default().with_typescript(true);
1224            // Should not panic whether `allow_v8_intrinsics` is set or not.
1225            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1226            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1227            assert_eq!(ret.diagnostics.len(), 1);
1228            let ret = Parser::new(&allocator, source, source_type).parse();
1229            assert_eq!(ret.diagnostics.len(), 1);
1230        }
1231    }
1232
1233    #[test]
1234    fn comments() {
1235        let allocator = Allocator::default();
1236        let source_type = SourceType::default().with_typescript(true);
1237        let sources = [
1238            ("// line comment", CommentKind::Line),
1239            ("/* line comment */", CommentKind::SingleLineBlock),
1240            (
1241                "type Foo = ( /* Require properties which are not generated automatically. */ 'bar')",
1242                CommentKind::SingleLineBlock,
1243            ),
1244        ];
1245        for (source, kind) in sources {
1246            let ret = Parser::new(&allocator, source, source_type).parse();
1247            let comments = &ret.program.comments;
1248            assert_eq!(comments.len(), 1, "{source}");
1249            assert_eq!(comments.first().unwrap().kind, kind, "{source}");
1250        }
1251    }
1252
1253    #[test]
1254    fn hashbang() {
1255        let allocator = Allocator::default();
1256        let source_type = SourceType::default();
1257        let source = "#!/usr/bin/node\n;";
1258        let ret = Parser::new(&allocator, source, source_type).parse();
1259        assert_eq!(ret.program.hashbang.unwrap().value.as_str(), "/usr/bin/node");
1260    }
1261
1262    #[test]
1263    fn unambiguous() {
1264        let allocator = Allocator::default();
1265        let source_type = SourceType::unambiguous();
1266        assert!(source_type.is_unambiguous());
1267        let sources = ["import x from 'foo';", "export {x} from 'foo';", "import.meta"];
1268        for source in sources {
1269            let ret = Parser::new(&allocator, source, source_type).parse();
1270            assert!(ret.program.source_type.is_module());
1271        }
1272
1273        let sources = ["", "import('foo')"];
1274        for source in sources {
1275            let ret = Parser::new(&allocator, source, source_type).parse();
1276            assert!(ret.program.source_type.is_script());
1277        }
1278    }
1279
1280    #[test]
1281    fn binary_file() {
1282        let allocator = Allocator::default();
1283        let source_type = SourceType::default();
1284
1285        // U+FFFD as a standalone token — file appears to be binary
1286        let ret = Parser::new(&allocator, "\u{FFFD}", source_type).parse();
1287        assert!(ret.program.is_empty());
1288        assert_eq!(ret.diagnostics.len(), 1);
1289        assert_eq!(ret.diagnostics[0].to_string(), "File appears to be binary.");
1290
1291        // U+FFFD inside string literals — should parse fine
1292        let ret = Parser::new(&allocator, "\"oops \u{FFFD} oops\";", source_type).parse();
1293        assert!(!ret.program.is_empty());
1294        assert!(ret.diagnostics.is_empty());
1295    }
1296
1297    #[test]
1298    fn memory_leak() {
1299        let allocator = Allocator::default();
1300        let source_type = SourceType::default();
1301        let sources = ["2n", ";'1234567890123456789012345678901234567890'"];
1302        for source in sources {
1303            let ret = Parser::new(&allocator, source, source_type).parse();
1304            assert!(!ret.program.body.is_empty());
1305        }
1306    }
1307
1308    // Source with length MAX_LEN + 1 fails to parse.
1309    // Skip this test on 32-bit systems as impossible to allocate a string longer than `isize::MAX`.
1310    // Also skip running under Miri since it takes so long.
1311    #[cfg(target_pointer_width = "64")]
1312    #[cfg(not(miri))]
1313    #[test]
1314    fn overlong_source() {
1315        use std::{
1316            alloc::{self, Layout},
1317            ptr::NonNull,
1318            slice, str,
1319        };
1320
1321        /// A string that has a length of `MAX_LEN + 1`, and is entirely zeros.
1322        ///
1323        /// We need to create a `&str` with `MAX_LEN + 1` length, but don't want to write 4 GiB of data,
1324        /// as it's too slow. This type uses `alloc_zeroed` which on most platforms will just create zeroed pages
1325        /// without actually writing any data, and so is much faster.
1326        struct ZeroedString {
1327            ptr: NonNull<u8>,
1328        }
1329
1330        impl ZeroedString {
1331            const LEN: usize = MAX_LEN + 1;
1332            const PAGE_SIZE: usize = 4096;
1333            const LAYOUT: Layout = match Layout::from_size_align(Self::LEN, Self::PAGE_SIZE) {
1334                Ok(layout) => layout,
1335                Err(_) => panic!("Failed to create layout"),
1336            };
1337
1338            fn new() -> Self {
1339                // SAFETY: `LAYOUT` is valid and non-zero size.
1340                let ptr = unsafe { alloc::alloc_zeroed(Self::LAYOUT) };
1341                let Some(ptr) = NonNull::new(ptr) else {
1342                    panic!("Failed to allocate {} bytes", Self::LEN);
1343                };
1344                Self { ptr }
1345            }
1346
1347            fn as_str(&self) -> &str {
1348                // SAFETY: `self.ptr` is pointer to start of `LEN` initialized and zeroed bytes.
1349                // A slice consisting entirely of zeros is valid UTF-8.
1350                unsafe {
1351                    str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), Self::LEN))
1352                }
1353            }
1354        }
1355
1356        impl Drop for ZeroedString {
1357            fn drop(&mut self) {
1358                // SAFETY: `self.ptr` is address of an allocation made with `LAYOUT`
1359                unsafe { alloc::dealloc(self.ptr.as_ptr(), Self::LAYOUT) };
1360            }
1361        }
1362
1363        // Create long source text (MAX_LEN + 1 bytes)
1364        let zeroed_string = ZeroedString::new();
1365        let source_text = zeroed_string.as_str();
1366
1367        // Attempt to parse the source text
1368        let allocator = Allocator::default();
1369        let ret = Parser::new(&allocator, source_text, SourceType::default()).parse();
1370
1371        // Parsing should fail
1372        assert!(ret.program.is_empty());
1373        assert!(ret.fatal_error);
1374        assert_eq!(ret.diagnostics.len(), 1);
1375        assert_eq!(
1376            ret.diagnostics.first().unwrap().to_string(),
1377            "Source length exceeds 4 GiB limit"
1378        );
1379    }
1380
1381    // Source with length MAX_LEN parses OK.
1382    // This test takes over 1 minute on an M1 Macbook Pro unless compiled in release mode.
1383    // `not(debug_assertions)` is a proxy for detecting release mode.
1384    // Also skip running under Miri since it takes so long.
1385    #[cfg(not(debug_assertions))]
1386    #[cfg(not(miri))]
1387    #[test]
1388    fn legal_length_source() {
1389        // Build a string MAX_LEN bytes long which doesn't take too long to parse
1390        let head = "const x = 1;\n/*";
1391        let foot = "*/\nconst y = 2;\n";
1392        let mut source = "x".repeat(MAX_LEN);
1393        source.replace_range(..head.len(), head);
1394        source.replace_range(MAX_LEN - foot.len().., foot);
1395        assert_eq!(source.len(), MAX_LEN);
1396
1397        let allocator = Allocator::default();
1398        let ret = Parser::new(&allocator, &source, SourceType::default()).parse();
1399        assert!(!ret.fatal_error);
1400        assert!(ret.diagnostics.is_empty());
1401        assert_eq!(ret.program.body.len(), 2);
1402    }
1403
1404    // Where the lexer cannot lex any further it emits `Undetermined`, and no `Eof` follows.
1405    // That final token ends the input just as `Eof` does, so it is discarded, and discarding it
1406    // must not take the real token before it with it.
1407    #[test]
1408    fn tokens_when_lexing_ends_in_error() {
1409        let allocator = Allocator::default();
1410        let ret = Parser::new(&allocator, "foo();\n'unterminated", SourceType::default())
1411            .with_config(config::TokensParserConfig)
1412            .parse();
1413
1414        assert!(!ret.fatal_error);
1415        assert_eq!(ret.diagnostics.len(), 1);
1416        assert_eq!(ret.diagnostics.first().unwrap().to_string(), "Unterminated string");
1417
1418        let tokens = ret
1419            .tokens
1420            .iter()
1421            .map(|token| (token.kind(), token.start(), token.end()))
1422            .collect::<Vec<_>>();
1423        assert_eq!(
1424            tokens,
1425            [
1426                (Kind::Ident, 0, 3),
1427                (Kind::LParen, 3, 4),
1428                (Kind::RParen, 4, 5),
1429                (Kind::Semicolon, 5, 6),
1430            ]
1431        );
1432    }
1433
1434    #[test]
1435    fn tokens_after_unambiguous_await_reparse() {
1436        for source in [
1437            "await /x/u; export {};",
1438            // Reparsing merges `/a/`, but splits `/b/g`, increasing the token count.
1439            "await /a/ /b/g; export {};",
1440            "before(); await /a/ /b/g; between(); await /x/u; export {}; tail();",
1441            "before(); await /x/u; after(); export {}; tail();",
1442            "before(); await /x/u; between(); await /y/g; export {}; tail();",
1443            "await /x/u\nawait /y/g\nexport {};",
1444            "await\nawait /x/u; export {};",
1445            "before(); await\nawait /x/u; between(); await\nawait /y/g; export {}; tail();",
1446            "const x = await /x/u; between(); const y = await /y/g; export {};",
1447            "/before/.test(await /x/u); /after/.test('after'); export {};",
1448            "await /x/u, import.meta;",
1449            "#!/usr/bin/env node\n'use strict';\nawait /* regexp */ /x/u; export {};",
1450            "export {}; await /x/u;",
1451        ] {
1452            let allocator = Allocator::default();
1453            let parse = |source_type| {
1454                Parser::new(&allocator, source, source_type)
1455                    .with_config(config::TokensParserConfig)
1456                    .parse()
1457            };
1458            let unambiguous = parse(SourceType::unambiguous());
1459            let module = parse(SourceType::mjs());
1460            assert_eq!(unambiguous.tokens.as_slice(), module.tokens.as_slice(), "{source}");
1461        }
1462    }
1463
1464    // A fatal error fast-forwards the lexer to end of file. Re-lexing the `}` of a template
1465    // substitution after that would derive a token position from the moved cursor, and so
1466    // overwrite an unrelated token in the collected stream.
1467    #[test]
1468    fn tokens_when_template_substitution_fails() {
1469        let allocator = Allocator::default();
1470        let ret = Parser::new(&allocator, "`${}`", SourceType::default())
1471            .with_config(config::TokensParserConfig)
1472            .parse();
1473
1474        assert!(ret.fatal_error);
1475        assert!(ret.tokens.is_empty());
1476    }
1477}