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