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
67// This third-party fork retains upstream implementation style. Workspace
68// membership is for distribution and regression tests, not Vize style rules.
69#![allow(clippy::all, clippy::wildcard_imports)]
70
71use std::any::Any;
72
73pub mod config;
74
75mod context;
76mod cursor;
77mod embedding;
78mod error_handler;
79mod modifiers;
80mod module_record;
81mod observation;
82mod state;
83
84mod js;
85mod jsx;
86mod ts;
87
88mod diagnostics;
89
90// Expose lexer only in benchmarks
91#[cfg(not(feature = "benchmarking"))]
92mod lexer;
93#[cfg(feature = "benchmarking")]
94#[doc(hidden)]
95pub mod lexer;
96
97use oxc_allocator::{Allocator, ArenaBox, ArenaVec, Dummy, GetAllocator};
98use oxc_ast::{
99    ast::{Expression, Program, Statement},
100    builder::{AstBuilder, GetAstBuilder},
101};
102use oxc_diagnostics::Diagnostics;
103use oxc_span::{SourceType, Span};
104use oxc_syntax::module_record::ModuleRecord;
105
106pub use crate::lexer::{Kind, Token};
107use crate::{
108    config::{
109        LexerConfig, NoTokensParserConfig, ParserConfig, RuntimeParserConfig, TokensParserConfig,
110    },
111    context::{Context, StatementContext},
112    diagnostics::ParserDiagnostic,
113    error_handler::FatalError,
114    lexer::Lexer,
115    module_record::ModuleRecordBuilder,
116    state::ParserState,
117};
118pub use embedding::{
119    AdmittedExpression, AdmittedHandlerBody, AdmittedParameters, EmbeddingGoal, EmbeddingHole,
120    EmbeddingInput, EmbeddingInputError, EmbeddingObservation, ExpressionObservation,
121    HandlerBodyObservation, ParametersObservation,
122};
123pub use observation::{AdmittedProgram, ProgramObservation};
124
125/// Maximum length of source which can be parsed (in bytes).
126/// ~4 GiB on 64-bit systems, ~2 GiB on 32-bit systems.
127// Length is constrained by 2 factors:
128// 1. `Span`'s `start` and `end` are `u32`s, which limits length to `u32::MAX` bytes.
129// 2. Rust's allocator APIs limit allocations to `isize::MAX`.
130// https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align
131pub(crate) const MAX_LEN: usize = if size_of::<usize>() >= 8 {
132    // 64-bit systems
133    u32::MAX as usize
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/// Oxc's [`Parser`] is able to recover from some syntax errors and continue parsing. When this
151/// happens,
152/// 1. [`diagnostics`] will be non-empty
153/// 2. [`program`] will contain a full AST
154/// 3. [`panicked`] will be false
155///
156/// When the parser cannot recover, it will abort and terminate parsing early. [`program`] will
157/// be empty and [`panicked`] will be `true`.
158///
159/// [`program`]: ParserReturn::program
160/// [`diagnostics`]: ParserReturn::diagnostics
161/// [`panicked`]: ParserReturn::panicked
162#[non_exhaustive]
163pub struct ParserReturn<'a> {
164    /// The parsed AST.
165    ///
166    /// Will be empty (e.g. no statements, directives, etc) if the parser panicked.
167    ///
168    /// ## Validity
169    /// It is possible for the AST to be present and semantically invalid. This will happen if
170    /// 1. The [`Parser`] encounters a recoverable syntax error
171    /// 2. The logic for checking the violation is in the semantic analyzer
172    ///
173    /// To ensure a valid AST, check that [`diagnostics`](ParserReturn::diagnostics) is empty. Then, run
174    /// semantic analysis with syntax error checking enabled.
175    pub program: Program<'a>,
176
177    /// See <https://tc39.es/ecma262/#sec-abstract-module-records>
178    pub module_record: ModuleRecord<'a>,
179
180    /// Syntax errors encountered while parsing.
181    ///
182    /// This list is not comprehensive. Oxc offloads more-expensive checks to [semantic
183    /// analysis](https://docs.rs/oxc_semantic), which can be enabled using
184    /// [`SemanticBuilder::with_check_syntax_error`](https://docs.rs/oxc_semantic/latest/oxc_semantic/struct.SemanticBuilder.html#method.with_check_syntax_error).
185    pub diagnostics: Diagnostics,
186
187    /// Irregular whitespaces for `Oxlint`
188    pub irregular_whitespaces: Box<[Span]>,
189
190    /// Lexed tokens in source order.
191    ///
192    /// Tokens are only collected when tokens are enabled in [`ParserConfig`].
193    pub tokens: ArenaVec<'a, Token>,
194
195    /// Whether the parser panicked and terminated early.
196    ///
197    /// This will be `false` if parsing was successful, or if parsing was able to recover from a
198    /// syntax error. When `true`, [`program`] will be empty and [`diagnostics`] will contain at least
199    /// one error.
200    ///
201    /// [`program`]: ParserReturn::program
202    /// [`diagnostics`]: ParserReturn::diagnostics
203    pub panicked: bool,
204
205    /// Whether the file is [flow](https://flow.org).
206    pub is_flow_language: bool,
207
208    // Private original lexer receipt, distinct from syntax diagnostics.
209    pub(crate) has_legacy_literals: bool,
210}
211
212/// Parse options
213///
214/// You may provide options to the [`Parser`] using [`Parser::with_options`].
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub struct ParseOptions {
217    /// Whether to parse regular expressions or not.
218    ///
219    /// Default: `false`
220    #[cfg(feature = "regular_expression")]
221    pub parse_regular_expression: bool,
222
223    /// Allow [`return`] statements outside of functions.
224    ///
225    /// By default, a return statement at the top level raises an error (`false`).
226    ///
227    /// Default: `false`
228    ///
229    /// [`return`]: oxc_ast::ast::ReturnStatement
230    pub allow_return_outside_function: bool,
231
232    /// Emit [`ParenthesizedExpression`]s and [`TSParenthesizedType`] in AST.
233    ///
234    /// If this option is `true`, parenthesized expressions are represented by
235    /// (non-standard) [`ParenthesizedExpression`] and [`TSParenthesizedType`] nodes
236    /// that have a single `expression` property containing the expression inside parentheses.
237    ///
238    /// Default: `true`
239    ///
240    /// [`ParenthesizedExpression`]: oxc_ast::ast::ParenthesizedExpression
241    /// [`TSParenthesizedType`]: oxc_ast::ast::TSParenthesizedType
242    pub preserve_parens: bool,
243
244    /// Allow V8 runtime calls in the AST.
245    /// See: [V8's Parser::ParseV8Intrinsic](https://chromium.googlesource.com/v8/v8/+/35a14c75e397302655d7b3fbe648f9490ae84b7d/src/parsing/parser.cc#4811).
246    ///
247    /// Default: `false`
248    ///
249    /// [`V8IntrinsicExpression`]: oxc_ast::ast::V8IntrinsicExpression
250    pub allow_v8_intrinsics: bool,
251
252    /// Precompute hashes for identifier names (`Ident`s) in the AST.
253    ///
254    /// Precomputed hashes speed up `Ident`-keyed hash map operations in semantic analysis
255    /// and later compilation stages, at a small cost to parsing speed.
256    ///
257    /// Only disable this for parse-only pipelines that never rely on `Ident` hashing, such as
258    /// parse + serialize or formatting. Unhashed `Ident`s do not compare equal to hashed ones,
259    /// so semantic analysis and anything else relying on `Ident` hashing must not run on an AST
260    /// parsed with this option disabled.
261    ///
262    /// Default: `true`
263    pub enable_ident_hashes: bool,
264}
265
266impl Default for ParseOptions {
267    fn default() -> Self {
268        Self {
269            #[cfg(feature = "regular_expression")]
270            parse_regular_expression: false,
271            allow_return_outside_function: false,
272            preserve_parens: true,
273            allow_v8_intrinsics: false,
274            enable_ident_hashes: true,
275        }
276    }
277}
278
279/// Recursive Descent Parser for ECMAScript and TypeScript
280///
281/// See [`Parser::parse`] for entry function.
282pub struct Parser<'a, C: ParserConfig = NoTokensParserConfig> {
283    allocator: &'a Allocator,
284    source_text: &'a str,
285    source_type: SourceType,
286    options: ParseOptions,
287    config: C,
288}
289
290impl<'a> Parser<'a> {
291    /// Create a new [`Parser`]
292    ///
293    /// # Parameters
294    /// - `allocator`: [Memory arena](oxc_allocator::Allocator) for allocating AST nodes
295    /// - `source_text`: Source code to parse
296    /// - `source_type`: Source type (e.g. JavaScript, TypeScript, JSX, ESM Module, Script)
297    pub fn new(allocator: &'a Allocator, source_text: &'a str, source_type: SourceType) -> Self {
298        let options = ParseOptions::default();
299        Self { allocator, source_text, source_type, options, config: NoTokensParserConfig }
300    }
301}
302
303impl<'a, C: ParserConfig> Parser<'a, C> {
304    /// Set parse options
305    #[must_use]
306    pub fn with_options(mut self, options: ParseOptions) -> Self {
307        self.options = options;
308        self
309    }
310
311    /// Set parser config.
312    ///
313    /// See [`ParserConfig`] for more details.
314    #[must_use]
315    pub fn with_config<Config: ParserConfig>(self, config: Config) -> Parser<'a, Config> {
316        Parser {
317            allocator: self.allocator,
318            source_text: self.source_text,
319            source_type: self.source_type,
320            options: self.options,
321            config,
322        }
323    }
324}
325
326mod parser_parse {
327    use super::*;
328
329    /// `UniquePromise` is a way to use the type system to enforce the invariant that only
330    /// a single `ParserImpl`, `Lexer` and `lexer::Source` can exist at any time on a thread.
331    /// This constraint is required to guarantee the soundness of some methods of these types
332    /// e.g. `Source::set_position`.
333    ///
334    /// `ParserImpl::new`, `Lexer::new` and `lexer::Source::new` all require a `UniquePromise`
335    /// to be provided to them. `UniquePromise::new` is not visible outside this module, so only
336    /// `Parser::parse` can create one, and it only calls `ParserImpl::new` once.
337    /// This enforces the invariant throughout the entire parser.
338    ///
339    /// `UniquePromise` is a zero-sized type and has no runtime cost. It's purely for the type-checker.
340    ///
341    /// `UniquePromise::new_for_tests_and_benchmarks` is a backdoor for tests/benchmarks, so they can
342    /// create a `ParserImpl` or `Lexer`, and manipulate it directly, for testing/benchmarking purposes.
343    pub struct UniquePromise(());
344
345    impl UniquePromise {
346        #[inline]
347        fn new() -> Self {
348            Self(())
349        }
350
351        /// Backdoor for tests/benchmarks to create a `UniquePromise` (see above).
352        /// This function must NOT be exposed outside of tests and benchmarks,
353        /// as it allows circumventing safety invariants of the parser.
354        #[cfg(any(test, feature = "benchmarking"))]
355        pub fn new_for_tests_and_benchmarks() -> Self {
356            Self(())
357        }
358    }
359
360    impl<'a, C: ParserConfig> Parser<'a, C> {
361        /// Main entry point
362        ///
363        /// Returns an empty `Program` on unrecoverable error,
364        /// Recoverable errors are stored inside `errors`.
365        ///
366        /// See the [module-level documentation](crate) for examples and more information.
367        //
368        // # Implementation note
369        //
370        // Dispatches via `Any` to a non-generic helper for each known `ParserConfig`.
371        // The dispatch keeps the parser body emitted exactly once in `oxc_parser`'s rlib,
372        // so consuming crates don't each get a private copy. The `Any::is` / `downcast_ref`
373        // calls const-fold when `C` is concrete (the trait object is built from a known
374        // concrete type at every monomorphization site, so LLVM devirtualises the vtable
375        // call to `Any::type_id` and folds the comparison), leaving each monomorphization
376        // as a single call into the matching helper.
377        pub fn parse(self) -> ParserReturn<'a> {
378            let config: &dyn Any = &self.config;
379            if config.is::<NoTokensParserConfig>() {
380                parse_with_no_tokens_config(
381                    self.allocator,
382                    self.source_text,
383                    self.source_type,
384                    self.options,
385                )
386            } else if config.is::<TokensParserConfig>() {
387                parse_with_tokens_config(
388                    self.allocator,
389                    self.source_text,
390                    self.source_type,
391                    self.options,
392                )
393            } else if let Some(&config) = config.downcast_ref::<RuntimeParserConfig>() {
394                parse_with_runtime_config(
395                    self.allocator,
396                    self.source_text,
397                    self.source_type,
398                    self.options,
399                    config,
400                )
401            } else {
402                // User-defined `ParserConfig`. Generic codegen here, monomorphized per consuming crate.
403                // Users using custom configs would need to perform the monomorphization themselves.
404                ParserImpl::<C>::new(
405                    self.allocator,
406                    self.source_text,
407                    self.source_type,
408                    self.options,
409                    self.config,
410                    UniquePromise::new(),
411                )
412                .parse()
413            }
414        }
415
416        /// Parse a single [`Expression`].
417        ///
418        /// # Example
419        ///
420        /// ```rust
421        /// use oxc_allocator::Allocator;
422        /// use oxc_ast::ast::Expression;
423        /// use oxc_parser::Parser;
424        /// use oxc_span::SourceType;
425        ///
426        /// let src = "let x = 1 + 2;";
427        /// let allocator = Allocator::new();
428        /// let source_type = SourceType::default();
429        ///
430        /// let expr: Expression<'_> = Parser::new(&allocator, src, source_type).parse_expression().unwrap();
431        /// ```
432        ///
433        /// # Errors
434        /// If the source code being parsed has syntax errors.
435        //
436        // # Implementation note
437        // Dispatches via `Any`, same as `parse` does.
438        pub fn parse_expression(self) -> Result<Expression<'a>, Diagnostics> {
439            let config: &dyn Any = &self.config;
440            if config.is::<NoTokensParserConfig>() {
441                parse_expression_with_no_tokens_config(
442                    self.allocator,
443                    self.source_text,
444                    self.source_type,
445                    self.options,
446                )
447            } else if config.is::<TokensParserConfig>() {
448                parse_expression_with_tokens_config(
449                    self.allocator,
450                    self.source_text,
451                    self.source_type,
452                    self.options,
453                )
454            } else if let Some(&config) = config.downcast_ref::<RuntimeParserConfig>() {
455                parse_expression_with_runtime_config(
456                    self.allocator,
457                    self.source_text,
458                    self.source_type,
459                    self.options,
460                    config,
461                )
462            } else {
463                ParserImpl::<C>::new(
464                    self.allocator,
465                    self.source_text,
466                    self.source_type,
467                    self.options,
468                    self.config,
469                    UniquePromise::new(),
470                )
471                .parse_expression()
472            }
473        }
474    }
475
476    // ===========================================================================
477    // Non-generic parse helpers, one per known `ParserConfig`.
478    //
479    // The parser is generic over `C: ParserConfig`. By default Rust monomorphizes
480    // generic functions per consuming crate (the legacy mangled name encodes the
481    // instantiating crate's disambiguator, and `share-generics` is off at
482    // `opt-level >= 2`). For a parser that's pulled in by ~15 crates in a real
483    // workspace, that means ~15 private copies of every parser method in the
484    // final cdylib — none of which can be deduped by COMDAT (different names)
485    // or fat LTO (slightly different inlining contexts).
486    //
487    // To avoid that, `Parser<C>::parse` and `Parser<C>::parse_expression` dispatch
488    // via `Any` to one of the helpers below for the three known configs.
489    // Each helper is non-generic, so it's emitted exactly once in `oxc_parser`'s
490    // rlib and shared by all consumers. The `Any::is` / `downcast_ref` checks fold
491    // at compile time when `C` is concrete, so each monomorphization of the dispatch
492    // shrinks to a single call into the matching helper.
493    //
494    // The helpers are `#[inline(never)]` to prevent fat LTO from re-inlining the
495    // parser body across the rlib boundary, which would defeat the purpose.
496    //
497    // For user-defined `ParserConfig` impls (rare), the dispatch falls through
498    // to a generic body that monomorphizes per consuming crate. That's the same
499    // cost the parser had without this pattern; we just keep that cost contained
500    // to the rare custom-config case.
501    // ===========================================================================
502
503    #[inline(never)]
504    fn parse_with_no_tokens_config<'a>(
505        allocator: &'a Allocator,
506        source_text: &'a str,
507        source_type: SourceType,
508        options: ParseOptions,
509    ) -> ParserReturn<'a> {
510        ParserImpl::<NoTokensParserConfig>::new(
511            allocator,
512            source_text,
513            source_type,
514            options,
515            NoTokensParserConfig,
516            UniquePromise::new(),
517        )
518        .parse()
519    }
520
521    #[inline(never)]
522    fn parse_with_tokens_config<'a>(
523        allocator: &'a Allocator,
524        source_text: &'a str,
525        source_type: SourceType,
526        options: ParseOptions,
527    ) -> ParserReturn<'a> {
528        ParserImpl::<TokensParserConfig>::new(
529            allocator,
530            source_text,
531            source_type,
532            options,
533            TokensParserConfig,
534            UniquePromise::new(),
535        )
536        .parse()
537    }
538
539    #[inline(never)]
540    fn parse_with_runtime_config<'a>(
541        allocator: &'a Allocator,
542        source_text: &'a str,
543        source_type: SourceType,
544        options: ParseOptions,
545        config: RuntimeParserConfig,
546    ) -> ParserReturn<'a> {
547        ParserImpl::<RuntimeParserConfig>::new(
548            allocator,
549            source_text,
550            source_type,
551            options,
552            config,
553            UniquePromise::new(),
554        )
555        .parse()
556    }
557
558    #[inline(never)]
559    fn parse_expression_with_no_tokens_config<'a>(
560        allocator: &'a Allocator,
561        source_text: &'a str,
562        source_type: SourceType,
563        options: ParseOptions,
564    ) -> Result<Expression<'a>, Diagnostics> {
565        ParserImpl::<NoTokensParserConfig>::new(
566            allocator,
567            source_text,
568            source_type,
569            options,
570            NoTokensParserConfig,
571            UniquePromise::new(),
572        )
573        .parse_expression()
574    }
575
576    #[inline(never)]
577    fn parse_expression_with_tokens_config<'a>(
578        allocator: &'a Allocator,
579        source_text: &'a str,
580        source_type: SourceType,
581        options: ParseOptions,
582    ) -> Result<Expression<'a>, Diagnostics> {
583        ParserImpl::<TokensParserConfig>::new(
584            allocator,
585            source_text,
586            source_type,
587            options,
588            TokensParserConfig,
589            UniquePromise::new(),
590        )
591        .parse_expression()
592    }
593
594    #[inline(never)]
595    fn parse_expression_with_runtime_config<'a>(
596        allocator: &'a Allocator,
597        source_text: &'a str,
598        source_type: SourceType,
599        options: ParseOptions,
600        config: RuntimeParserConfig,
601    ) -> Result<Expression<'a>, Diagnostics> {
602        ParserImpl::<RuntimeParserConfig>::new(
603            allocator,
604            source_text,
605            source_type,
606            options,
607            config,
608            UniquePromise::new(),
609        )
610        .parse_expression()
611    }
612}
613use parser_parse::UniquePromise;
614
615/// Implementation of parser.
616/// `Parser` is just a public wrapper, the guts of the implementation is in this type.
617struct ParserImpl<'a, C: ParserConfig> {
618    /// Options
619    options: ParseOptions,
620
621    pub(crate) lexer: Lexer<'a, C::LexerConfig>,
622
623    /// SourceType: JavaScript or TypeScript, Script or Module, jsx support?
624    source_type: SourceType,
625
626    /// Source Code
627    source_text: &'a str,
628
629    /// All syntax errors from parser and lexer
630    /// Note: favor adding to `Diagnostics` instead of raising Err
631    ///
632    /// Stored in deferred (unmaterialized) form and materialized once at parse exit.
633    errors: Vec<ParserDiagnostic<'a>>,
634
635    /// Errors that are only valid if the file is determined to be a Script (not a Module).
636    /// For `ModuleKind::Unambiguous`, we defer ESM-only errors (like top-level await)
637    /// until we know whether the file is ESM or Script.
638    /// If resolved to Module → discard these errors.
639    /// If resolved to Script → emit these errors.
640    deferred_script_errors: Vec<ParserDiagnostic<'a>>,
641
642    fatal_error: Option<FatalError<'a>>,
643
644    /// The current parsing token
645    token: Token,
646
647    /// The end range of the previous token
648    prev_token_end: u32,
649
650    /// Parser state
651    state: ParserState<'a>,
652
653    /// Parsing context
654    ctx: Context,
655
656    /// Ast builder for creating AST nodes
657    ast: AstBuilder<'a>,
658
659    /// Module Record Builder
660    module_record_builder: ModuleRecordBuilder<'a>,
661
662    /// Precomputed typescript detection
663    is_ts: bool,
664}
665
666impl<'a, C: ParserConfig> ParserImpl<'a, C> {
667    /// Create a new `ParserImpl`.
668    ///
669    /// Requiring a `UniquePromise` to be provided guarantees only 1 `ParserImpl` can exist
670    /// on a single thread at one time.
671    #[inline]
672    #[expect(clippy::needless_pass_by_value)]
673    pub fn new(
674        allocator: &'a Allocator,
675        source_text: &'a str,
676        source_type: SourceType,
677        options: ParseOptions,
678        config: C,
679        unique: UniquePromise,
680    ) -> Self {
681        Self {
682            options,
683            lexer: Lexer::new(allocator, source_text, source_type, config.lexer_config(), unique),
684            source_type,
685            source_text,
686            errors: vec![],
687            deferred_script_errors: vec![],
688            fatal_error: None,
689            token: Token::default(),
690            prev_token_end: 0,
691            state: ParserState::new(),
692            ctx: Self::default_context(source_type, options),
693            ast: AstBuilder::new(allocator),
694            module_record_builder: ModuleRecordBuilder::new(allocator, source_type),
695            is_ts: source_type.is_typescript(),
696        }
697    }
698
699    /// Main entry point
700    ///
701    /// Returns an empty `Program` on unrecoverable error,
702    /// Recoverable errors are stored inside `errors`.
703    #[inline]
704    pub fn parse(mut self) -> ParserReturn<'a> {
705        let mut program = self.parse_program();
706        let mut panicked = false;
707
708        if let Some(fatal_error) = self.fatal_error.take() {
709            panicked = true;
710            self.errors.truncate(fatal_error.errors_len);
711            if !self.lexer.errors.is_empty() && self.cur_kind().is_eof() {
712                // Noop
713            } else {
714                self.error(fatal_error.error);
715            }
716
717            program = Program::dummy(self.allocator());
718            program.source_type = self.source_type;
719            program.source_text = self.source_text;
720        }
721
722        self.check_unfinished_errors();
723
724        if let Some(overlong_error) = self.overlong_error() {
725            panicked = true;
726            self.lexer.errors.clear();
727            self.errors.clear();
728            self.error(overlong_error);
729        }
730
731        let mut is_flow_language = false;
732        let mut errors = Diagnostics::new();
733        // only check for `@flow` if the file failed to parse.
734        if (!self.lexer.errors.is_empty() || !self.errors.is_empty())
735            && let Some(error) = self.flow_error()
736        {
737            is_flow_language = true;
738            errors.push(error.into_diagnostic());
739        }
740        let (module_record, mut module_record_errors) = self.module_record_builder.build();
741        if errors.len() != 1 {
742            errors
743                .reserve(self.lexer.errors.len() + self.errors.len() + module_record_errors.len());
744            errors.extend(self.lexer.errors.drain(..).map(ParserDiagnostic::into_diagnostic));
745            errors.extend(self.errors.drain(..).map(ParserDiagnostic::into_diagnostic));
746            errors.append(&mut module_record_errors);
747        }
748        let irregular_whitespaces =
749            std::mem::take(&mut self.lexer.trivia_builder.irregular_whitespaces).into_boxed_slice();
750
751        let source_type = program.source_type;
752        if source_type.is_unambiguous() {
753            if module_record.has_module_syntax {
754                // Resolved to Module - discard deferred script errors (TLA is valid in ESM)
755                // but emit deferred module errors (HTML comments are invalid in ESM)
756                program.source_type = source_type.with_module(true);
757                errors.extend(
758                    self.lexer
759                        .deferred_module_errors
760                        .drain(..)
761                        .map(ParserDiagnostic::into_diagnostic),
762                );
763            } else {
764                // Resolved to Script - emit deferred script errors
765                // discard deferred module errors (HTML comments are valid in scripts)
766                program.source_type = source_type.with_script(true);
767                errors.extend(
768                    self.deferred_script_errors.into_iter().map(ParserDiagnostic::into_diagnostic),
769                );
770            }
771        }
772
773        let tokens =
774            if panicked { ArenaVec::new_in(&self.ast) } else { self.lexer.finalize_tokens() };
775
776        program.comments = self.lexer.trivia_builder.comments;
777
778        ParserReturn {
779            program,
780            module_record,
781            diagnostics: errors,
782            irregular_whitespaces,
783            tokens,
784            panicked,
785            is_flow_language,
786            has_legacy_literals: self.lexer.has_legacy_literals,
787        }
788    }
789
790    pub fn parse_expression(mut self) -> Result<Expression<'a>, Diagnostics> {
791        // initialize cur_token and prev_token by moving onto the first token
792        self.bump_any();
793        let expr = self.parse_expr();
794        if let Some(FatalError { error, .. }) = self.fatal_error.take() {
795            return Err(error.into_diagnostic().into());
796        }
797        self.check_unfinished_errors();
798        let errors = self
799            .lexer
800            .errors
801            .into_iter()
802            .chain(self.errors)
803            .map(ParserDiagnostic::into_diagnostic)
804            .collect::<Diagnostics>();
805        if !errors.is_empty() {
806            return Err(errors);
807        }
808        Ok(expr)
809    }
810
811    #[expect(clippy::cast_possible_truncation)]
812    fn parse_program(&mut self) -> Program<'a> {
813        // Initialize by moving onto the first token.
814        // Checks for hashbang comment.
815        self.token = self.lexer.first_token();
816
817        let hashbang = self.parse_hashbang();
818        self.ctx |= Context::TopLevel;
819        let (directives, mut statements) =
820            self.parse_directives_and_statements(/* in_ts_namespace_body */ false);
821
822        // In unambiguous mode, if ESM syntax was detected (import/export/import.meta),
823        // we need to reparse statements that were originally parsed with `await` as identifier.
824        // TypeScript's behavior: initially parse `await /x/` as division, then reparse as
825        // await expression with regex when ESM is detected.
826        if self.source_type.is_unambiguous()
827            && self.module_record_builder.has_module_syntax()
828            && !self.state.potential_await_reparse.is_empty()
829        {
830            self.reparse_potential_top_level_awaits(&mut statements);
831        }
832
833        let span = Span::new(0, self.source_text.len() as u32);
834        Program::new(
835            span,
836            self.source_type,
837            self.source_text,
838            // Populated at the end of `parse` after `flow_error` has read from `trivia_builder.comments`
839            [],
840            hashbang,
841            directives,
842            statements,
843            self,
844        )
845    }
846
847    /// Reparse statements that may contain top-level await expressions.
848    ///
849    /// In unambiguous mode, statements like `await /x/u` are initially parsed as
850    /// `await / x / u` (identifier with divisions). If ESM syntax is detected,
851    /// we need to reparse them with the await context enabled.
852    fn reparse_potential_top_level_awaits(&mut self, statements: &mut ArenaVec<'a, Statement<'a>>) {
853        // Token stream is already complete from the first parse.
854        // Reparsing here is only to patch AST nodes, so keep the original token stream.
855        let original_tokens =
856            if self.lexer.config.tokens() { Some(self.lexer.take_tokens()) } else { None };
857
858        // A committed selective reparse must not discard the completed tail's
859        // original literal receipt when it rewinds an earlier statement.
860        let original_legacy_literals = self.lexer.has_legacy_literals;
861        let checkpoints = std::mem::take(&mut self.state.potential_await_reparse);
862        for (stmt_index, checkpoint) in checkpoints {
863            // Rewind to the checkpoint
864            self.rewind(checkpoint);
865
866            // Parse the statement with await context enabled (TopLevel context is already set)
867            let stmt = self.context_add(Context::Await, |p| {
868                p.parse_statement_list_item(StatementContext::StatementList)
869            });
870
871            // Replace the statement if the index is valid
872            if stmt_index < statements.len() {
873                statements[stmt_index] = stmt;
874            }
875        }
876
877        self.lexer.has_legacy_literals |= original_legacy_literals;
878        if let Some(original_tokens) = original_tokens {
879            self.lexer.set_tokens(original_tokens);
880        }
881    }
882
883    fn default_context(source_type: SourceType, options: ParseOptions) -> Context {
884        let mut ctx = Context::default().and_ambient(source_type.is_typescript_definition());
885        if source_type.is_module() {
886            // for [top-level-await](https://tc39.es/proposal-top-level-await/)
887            ctx = ctx.and_await(true);
888        }
889        // CommonJS files are wrapped in a function, so return and `new.target`
890        // are allowed at top-level
891        if options.allow_return_outside_function || source_type.is_commonjs() {
892            ctx = ctx.and_return(true);
893        }
894        if source_type.is_commonjs() {
895            ctx = ctx.and_new_target(true);
896        }
897        ctx
898    }
899
900    /// Check for Flow declaration if the file cannot be parsed.
901    /// The declaration must be [on the first line before any code](https://flow.org/en/docs/usage/#toc-prepare-your-code-for-flow)
902    fn flow_error(&mut self) -> Option<ParserDiagnostic<'a>> {
903        if !self.source_type.is_javascript() {
904            return None;
905        }
906        let span = self.lexer.trivia_builder.comments.first()?.span;
907        if span.source_text(self.source_text).contains("@flow") {
908            self.errors.clear();
909            Some(diagnostics::flow(span))
910        } else {
911            None
912        }
913    }
914
915    fn check_unfinished_errors(&mut self) {
916        use oxc_span::GetSpan;
917        // PropertyDefinition : cover_initialized_name
918        // It is a Syntax Error if any source text is matched by this production.
919        for expr in self.state.cover_initialized_name.values() {
920            self.errors.push(diagnostics::cover_initialized_name(expr.span()));
921        }
922    }
923
924    /// Check if source length exceeds MAX_LEN, if the file cannot be parsed.
925    /// Original parsing error is not real - `Lexer::new` substituted "\0" as the source text.
926    #[cold]
927    fn overlong_error(&self) -> Option<ParserDiagnostic<'a>> {
928        if self.source_text.len() > MAX_LEN {
929            return Some(diagnostics::overlong_source());
930        }
931        None
932    }
933
934    #[inline]
935    fn alloc<T>(&self, value: T) -> ArenaBox<'a, T> {
936        ArenaBox::new_in(value, self)
937    }
938}
939
940impl<'a, C: ParserConfig> GetAllocator<'a> for ParserImpl<'a, C> {
941    #[inline]
942    fn allocator(&self) -> &'a Allocator {
943        self.ast.allocator()
944    }
945}
946
947impl<'a, C: ParserConfig> GetAstBuilder<'a> for ParserImpl<'a, C> {
948    type Builder = AstBuilder<'a>;
949
950    #[inline]
951    fn builder(&self) -> &AstBuilder<'a> {
952        &self.ast
953    }
954}
955
956#[cfg(test)]
957mod test {
958    use std::path::Path;
959
960    use oxc_ast::ast::{CommentKind, Expression, Statement};
961    use oxc_span::GetSpan;
962
963    use super::*;
964
965    #[test]
966    fn parse_program_smoke_test() {
967        let allocator = Allocator::default();
968        let source_type = SourceType::default();
969        let source = "";
970        let ret = Parser::new(&allocator, source, source_type).parse();
971        assert!(ret.program.is_empty());
972        assert!(ret.diagnostics.is_empty());
973        assert!(!ret.is_flow_language);
974    }
975
976    #[test]
977    fn parse_expression_smoke_test() {
978        let allocator = Allocator::default();
979        let source_type = SourceType::default();
980        let source = "a";
981        let expr = Parser::new(&allocator, source, source_type).parse_expression().unwrap();
982        assert!(matches!(expr, Expression::Identifier(_)));
983    }
984
985    #[test]
986    fn flow_error() {
987        let allocator = Allocator::default();
988        let source_type = SourceType::default();
989        let sources = [
990            "// @flow\nasdf adsf",
991            "/* @flow */\n asdf asdf",
992            "/**
993             * @flow
994             */
995             asdf asdf
996             ",
997            "/* @flow */ super;",
998        ];
999        for source in sources {
1000            let ret = Parser::new(&allocator, source, source_type).parse();
1001            assert!(ret.is_flow_language);
1002            assert_eq!(ret.diagnostics.len(), 1);
1003            assert_eq!(ret.diagnostics.first().unwrap().to_string(), "Flow is not supported");
1004        }
1005    }
1006
1007    #[test]
1008    fn ts_module_declaration() {
1009        let allocator = Allocator::default();
1010        let source_type = SourceType::from_path(Path::new("module.ts")).unwrap();
1011        let source = "declare module 'test'\n";
1012        let ret = Parser::new(&allocator, source, source_type).parse();
1013        assert_eq!(ret.diagnostics.len(), 0);
1014    }
1015
1016    #[test]
1017    fn directives() {
1018        let allocator = Allocator::default();
1019        let source_type = SourceType::default();
1020        let sources = [
1021            ("import x from 'foo'; 'use strict';", 2),
1022            ("export {x} from 'foo'; 'use strict';", 2),
1023            (";'use strict';", 2),
1024        ];
1025        for (source, body_length) in sources {
1026            let ret = Parser::new(&allocator, source, source_type).parse();
1027            assert!(ret.program.directives.is_empty(), "{source}");
1028            assert_eq!(ret.program.body.len(), body_length, "{source}");
1029        }
1030    }
1031
1032    #[test]
1033    fn v8_intrinsics() {
1034        let allocator = Allocator::default();
1035        let source_type = SourceType::default();
1036        {
1037            let source = "%DebugPrint('Raging against the Dying Light')";
1038            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1039            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1040            assert!(ret.diagnostics.is_empty());
1041
1042            if let Some(Statement::ExpressionStatement(expr_stmt)) = ret.program.body.first() {
1043                if let Expression::V8IntrinsicExpression(expr) = &expr_stmt.expression {
1044                    assert_eq!(expr.span().source_text(source), source);
1045                } else {
1046                    panic!("Expected V8IntrinsicExpression");
1047                }
1048            } else {
1049                panic!("Expected ExpressionStatement");
1050            }
1051        }
1052        {
1053            let source = "%DebugPrint(...illegalSpread)";
1054            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1055            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1056            assert_eq!(ret.diagnostics.len(), 1);
1057            assert_eq!(
1058                ret.diagnostics[0].to_string(),
1059                "V8 runtime calls cannot have spread elements as arguments"
1060            );
1061        }
1062        {
1063            let source = "%DebugPrint('~~')";
1064            let ret = Parser::new(&allocator, source, source_type).parse();
1065            assert_eq!(ret.diagnostics.len(), 1);
1066            assert_eq!(ret.diagnostics[0].to_string(), "Unexpected token");
1067        }
1068        {
1069            // https://github.com/oxc-project/oxc/issues/12121
1070            let source = "interface Props extends %enuProps {}";
1071            let source_type = SourceType::default().with_typescript(true);
1072            // Should not panic whether `allow_v8_intrinsics` is set or not.
1073            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
1074            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
1075            assert_eq!(ret.diagnostics.len(), 1);
1076            let ret = Parser::new(&allocator, source, source_type).parse();
1077            assert_eq!(ret.diagnostics.len(), 1);
1078        }
1079    }
1080
1081    #[test]
1082    fn comments() {
1083        let allocator = Allocator::default();
1084        let source_type = SourceType::default().with_typescript(true);
1085        let sources = [
1086            ("// line comment", CommentKind::Line),
1087            ("/* line comment */", CommentKind::SingleLineBlock),
1088            (
1089                "type Foo = ( /* Require properties which are not generated automatically. */ 'bar')",
1090                CommentKind::SingleLineBlock,
1091            ),
1092        ];
1093        for (source, kind) in sources {
1094            let ret = Parser::new(&allocator, source, source_type).parse();
1095            let comments = &ret.program.comments;
1096            assert_eq!(comments.len(), 1, "{source}");
1097            assert_eq!(comments.first().unwrap().kind, kind, "{source}");
1098        }
1099    }
1100
1101    #[test]
1102    fn hashbang() {
1103        let allocator = Allocator::default();
1104        let source_type = SourceType::default();
1105        let source = "#!/usr/bin/node\n;";
1106        let ret = Parser::new(&allocator, source, source_type).parse();
1107        assert_eq!(ret.program.hashbang.unwrap().value.as_str(), "/usr/bin/node");
1108    }
1109
1110    #[test]
1111    fn unambiguous() {
1112        let allocator = Allocator::default();
1113        let source_type = SourceType::unambiguous();
1114        assert!(source_type.is_unambiguous());
1115        let sources = ["import x from 'foo';", "export {x} from 'foo';", "import.meta"];
1116        for source in sources {
1117            let ret = Parser::new(&allocator, source, source_type).parse();
1118            assert!(ret.program.source_type.is_module());
1119        }
1120
1121        let sources = ["", "import('foo')"];
1122        for source in sources {
1123            let ret = Parser::new(&allocator, source, source_type).parse();
1124            assert!(ret.program.source_type.is_script());
1125        }
1126    }
1127
1128    #[test]
1129    fn binary_file() {
1130        let allocator = Allocator::default();
1131        let source_type = SourceType::default();
1132
1133        // U+FFFD as a standalone token — file appears to be binary
1134        let ret = Parser::new(&allocator, "\u{FFFD}", source_type).parse();
1135        assert!(ret.program.is_empty());
1136        assert_eq!(ret.diagnostics.len(), 1);
1137        assert_eq!(ret.diagnostics[0].to_string(), "File appears to be binary.");
1138
1139        // U+FFFD inside string literals — should parse fine
1140        let ret = Parser::new(&allocator, "\"oops \u{FFFD} oops\";", source_type).parse();
1141        assert!(!ret.program.is_empty());
1142        assert!(ret.diagnostics.is_empty());
1143    }
1144
1145    #[test]
1146    fn memory_leak() {
1147        let allocator = Allocator::default();
1148        let source_type = SourceType::default();
1149        let sources = ["2n", ";'1234567890123456789012345678901234567890'"];
1150        for source in sources {
1151            let ret = Parser::new(&allocator, source, source_type).parse();
1152            assert!(!ret.program.body.is_empty());
1153        }
1154    }
1155
1156    // Source with length MAX_LEN + 1 fails to parse.
1157    // Skip this test on 32-bit systems as impossible to allocate a string longer than `isize::MAX`.
1158    // Also skip running under Miri since it takes so long.
1159    #[cfg(target_pointer_width = "64")]
1160    #[cfg(not(miri))]
1161    #[test]
1162    fn overlong_source() {
1163        use std::{
1164            alloc::{self, Layout},
1165            ptr::NonNull,
1166            slice, str,
1167        };
1168
1169        /// A string that has a length of `MAX_LEN + 1`, and is entirely zeros.
1170        ///
1171        /// We need to create a `&str` with `MAX_LEN + 1` length, but don't want to write 4 GiB of data,
1172        /// as it's too slow. This type uses `alloc_zeroed` which on most platforms will just create zeroed pages
1173        /// without actually writing any data, and so is much faster.
1174        struct ZeroedString {
1175            ptr: NonNull<u8>,
1176        }
1177
1178        impl ZeroedString {
1179            const LEN: usize = MAX_LEN + 1;
1180            const PAGE_SIZE: usize = 4096;
1181            const LAYOUT: Layout = match Layout::from_size_align(Self::LEN, Self::PAGE_SIZE) {
1182                Ok(layout) => layout,
1183                Err(_) => panic!("Failed to create layout"),
1184            };
1185
1186            fn new() -> Self {
1187                // SAFETY: `LAYOUT` is valid and non-zero size.
1188                let ptr = unsafe { alloc::alloc_zeroed(Self::LAYOUT) };
1189                let Some(ptr) = NonNull::new(ptr) else {
1190                    panic!("Failed to allocate {} bytes", Self::LEN);
1191                };
1192                Self { ptr }
1193            }
1194
1195            fn as_str(&self) -> &str {
1196                // SAFETY: `self.ptr` is pointer to start of `LEN` initialized and zeroed bytes.
1197                // A slice consisting entirely of zeros is valid UTF-8.
1198                unsafe {
1199                    str::from_utf8_unchecked(slice::from_raw_parts(self.ptr.as_ptr(), Self::LEN))
1200                }
1201            }
1202        }
1203
1204        impl Drop for ZeroedString {
1205            fn drop(&mut self) {
1206                // SAFETY: `self.ptr` is address of an allocation made with `LAYOUT`
1207                unsafe { alloc::dealloc(self.ptr.as_ptr(), Self::LAYOUT) };
1208            }
1209        }
1210
1211        // Create long source text (MAX_LEN + 1 bytes)
1212        let zeroed_string = ZeroedString::new();
1213        let source_text = zeroed_string.as_str();
1214
1215        // Attempt to parse the source text
1216        let allocator = Allocator::default();
1217        let ret = Parser::new(&allocator, source_text, SourceType::default()).parse();
1218
1219        // Parsing should fail
1220        assert!(ret.program.is_empty());
1221        assert!(ret.panicked);
1222        assert_eq!(ret.diagnostics.len(), 1);
1223        assert_eq!(
1224            ret.diagnostics.first().unwrap().to_string(),
1225            "Source length exceeds 4 GiB limit"
1226        );
1227    }
1228
1229    // Source with length MAX_LEN parses OK.
1230    // This test takes over 1 minute on an M1 Macbook Pro unless compiled in release mode.
1231    // `not(debug_assertions)` is a proxy for detecting release mode.
1232    // Also skip running under Miri since it takes so long.
1233    #[cfg(not(debug_assertions))]
1234    #[cfg(not(miri))]
1235    #[test]
1236    fn legal_length_source() {
1237        // Build a string MAX_LEN bytes long which doesn't take too long to parse
1238        let head = "const x = 1;\n/*";
1239        let foot = "*/\nconst y = 2;\n";
1240        let mut source = "x".repeat(MAX_LEN);
1241        source.replace_range(..head.len(), head);
1242        source.replace_range(MAX_LEN - foot.len().., foot);
1243        assert_eq!(source.len(), MAX_LEN);
1244
1245        let allocator = Allocator::default();
1246        let ret = Parser::new(&allocator, &source, SourceType::default()).parse();
1247        assert!(!ret.panicked);
1248        assert!(ret.diagnostics.is_empty());
1249        assert_eq!(ret.program.body.len(), 2);
1250    }
1251}