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