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