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