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
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 ([bumpalo](https://docs.rs/bumpalo)) 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
40#![doc = include_str!("../examples/parser.rs")]
41//! ```
42//!
43//! ### Parsing TSX
44//! ```rust
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
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#![warn(missing_docs)]
68
69mod context;
70mod cursor;
71mod error_handler;
72mod modifiers;
73mod module_record;
74mod state;
75
76mod js;
77mod jsx;
78mod ts;
79
80mod diagnostics;
81
82// Expose lexer only in benchmarks
83#[cfg(not(feature = "benchmarking"))]
84mod lexer;
85#[cfg(feature = "benchmarking")]
86#[doc(hidden)]
87pub mod lexer;
88
89use oxc_allocator::{Allocator, Box as ArenaBox, Dummy};
90use oxc_ast::{
91    AstBuilder,
92    ast::{Expression, Program},
93};
94use oxc_diagnostics::OxcDiagnostic;
95use oxc_span::{ModuleKind, SourceType, Span};
96use oxc_syntax::module_record::ModuleRecord;
97
98use crate::{
99    context::{Context, StatementContext},
100    error_handler::FatalError,
101    lexer::{Lexer, Token},
102    module_record::ModuleRecordBuilder,
103    state::ParserState,
104};
105
106/// Maximum length of source which can be parsed (in bytes).
107/// ~4 GiB on 64-bit systems, ~2 GiB on 32-bit systems.
108// Length is constrained by 2 factors:
109// 1. `Span`'s `start` and `end` are `u32`s, which limits length to `u32::MAX` bytes.
110// 2. Rust's allocator APIs limit allocations to `isize::MAX`.
111// https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align
112pub(crate) const MAX_LEN: usize = if size_of::<usize>() >= 8 {
113    // 64-bit systems
114    u32::MAX as usize
115} else {
116    // 32-bit or 16-bit systems
117    isize::MAX as usize
118};
119
120/// Return value of [`Parser::parse`] consisting of AST, errors and comments
121///
122/// ## AST Validity
123///
124/// [`program`] will always contain a structurally valid AST, even if there are syntax errors.
125/// However, the AST may be semantically invalid. To ensure a valid AST,
126/// 1. Check that [`errors`] is empty
127/// 2. Run semantic analysis with [syntax error checking
128///    enabled](https://docs.rs/oxc_semantic/latest/oxc_semantic/struct.SemanticBuilder.html#method.with_check_syntax_error)
129///
130/// ## Errors
131/// Oxc's [`Parser`] is able to recover from some syntax errors and continue parsing. When this
132/// happens,
133/// 1. [`errors`] will be non-empty
134/// 2. [`program`] will contain a full AST
135/// 3. [`panicked`] will be false
136///
137/// When the parser cannot recover, it will abort and terminate parsing early. [`program`] will
138/// be empty and [`panicked`] will be `true`.
139///
140/// [`program`]: ParserReturn::program
141/// [`errors`]: ParserReturn::errors
142/// [`panicked`]: ParserReturn::panicked
143#[non_exhaustive]
144pub struct ParserReturn<'a> {
145    /// The parsed AST.
146    ///
147    /// Will be empty (e.g. no statements, directives, etc) if the parser panicked.
148    ///
149    /// ## Validity
150    /// It is possible for the AST to be present and semantically invalid. This will happen if
151    /// 1. The [`Parser`] encounters a recoverable syntax error
152    /// 2. The logic for checking the violation is in the semantic analyzer
153    ///
154    /// To ensure a valid AST, check that [`errors`](ParserReturn::errors) is empty. Then, run
155    /// semantic analysis with syntax error checking enabled.
156    pub program: Program<'a>,
157
158    /// See <https://tc39.es/ecma262/#sec-abstract-module-records>
159    pub module_record: ModuleRecord<'a>,
160
161    /// Syntax errors encountered while parsing.
162    ///
163    /// This list is not comprehensive. Oxc offloads more-expensive checks to [semantic
164    /// analysis](https://docs.rs/oxc_semantic), which can be enabled using
165    /// [`SemanticBuilder::with_check_syntax_error`](https://docs.rs/oxc_semantic/latest/oxc_semantic/struct.SemanticBuilder.html#method.with_check_syntax_error).
166    pub errors: Vec<OxcDiagnostic>,
167
168    /// Irregular whitespaces for `Oxlint`
169    pub irregular_whitespaces: Box<[Span]>,
170
171    /// Whether the parser panicked and terminated early.
172    ///
173    /// This will be `false` if parsing was successful, or if parsing was able to recover from a
174    /// syntax error. When `true`, [`program`] will be empty and [`errors`] will contain at least
175    /// one error.
176    ///
177    /// [`program`]: ParserReturn::program
178    /// [`errors`]: ParserReturn::errors
179    pub panicked: bool,
180
181    /// Whether the file is [flow](https://flow.org).
182    pub is_flow_language: bool,
183}
184
185/// Parse options
186///
187/// You may provide options to the [`Parser`] using [`Parser::with_options`].
188#[derive(Debug, Clone, Copy)]
189pub struct ParseOptions {
190    /// Whether to parse regular expressions or not.
191    ///
192    /// Default: `false`
193    #[cfg(feature = "regular_expression")]
194    pub parse_regular_expression: bool,
195
196    /// Allow [`return`] statements outside of functions.
197    ///
198    /// By default, a return statement at the top level raises an error (`false`).
199    ///
200    /// Default: `false`
201    ///
202    /// [`return`]: oxc_ast::ast::ReturnStatement
203    pub allow_return_outside_function: bool,
204
205    /// Emit [`ParenthesizedExpression`]s and [`TSParenthesizedType`] in AST.
206    ///
207    /// If this option is `true`, parenthesized expressions are represented by
208    /// (non-standard) [`ParenthesizedExpression`] and [`TSParenthesizedType`] nodes
209    /// that have a single `expression` property containing the expression inside parentheses.
210    ///
211    /// Default: `true`
212    ///
213    /// [`ParenthesizedExpression`]: oxc_ast::ast::ParenthesizedExpression
214    /// [`TSParenthesizedType`]: oxc_ast::ast::TSParenthesizedType
215    pub preserve_parens: bool,
216
217    /// Allow V8 runtime calls in the AST.
218    /// See: [V8's Parser::ParseV8Intrinsic](https://chromium.googlesource.com/v8/v8/+/35a14c75e397302655d7b3fbe648f9490ae84b7d/src/parsing/parser.cc#4811).
219    ///
220    /// Default: `false`
221    ///
222    /// [`V8IntrinsicExpression`]: oxc_ast::ast::V8IntrinsicExpression
223    pub allow_v8_intrinsics: bool,
224}
225
226impl Default for ParseOptions {
227    fn default() -> Self {
228        Self {
229            #[cfg(feature = "regular_expression")]
230            parse_regular_expression: false,
231            allow_return_outside_function: false,
232            preserve_parens: true,
233            allow_v8_intrinsics: false,
234        }
235    }
236}
237
238/// Recursive Descent Parser for ECMAScript and TypeScript
239///
240/// See [`Parser::parse`] for entry function.
241pub struct Parser<'a> {
242    allocator: &'a Allocator,
243    source_text: &'a str,
244    source_type: SourceType,
245    options: ParseOptions,
246}
247
248impl<'a> Parser<'a> {
249    /// Create a new [`Parser`]
250    ///
251    /// # Parameters
252    /// - `allocator`: [Memory arena](oxc_allocator::Allocator) for allocating AST nodes
253    /// - `source_text`: Source code to parse
254    /// - `source_type`: Source type (e.g. JavaScript, TypeScript, JSX, ESM Module, Script)
255    pub fn new(allocator: &'a Allocator, source_text: &'a str, source_type: SourceType) -> Self {
256        let options = ParseOptions::default();
257        Self { allocator, source_text, source_type, options }
258    }
259
260    /// Set parse options
261    #[must_use]
262    pub fn with_options(mut self, options: ParseOptions) -> Self {
263        self.options = options;
264        self
265    }
266}
267
268mod parser_parse {
269    use super::*;
270
271    /// `UniquePromise` is a way to use the type system to enforce the invariant that only
272    /// a single `ParserImpl`, `Lexer` and `lexer::Source` can exist at any time on a thread.
273    /// This constraint is required to guarantee the soundness of some methods of these types
274    /// e.g. `Source::set_position`.
275    ///
276    /// `ParserImpl::new`, `Lexer::new` and `lexer::Source::new` all require a `UniquePromise`
277    /// to be provided to them. `UniquePromise::new` is not visible outside this module, so only
278    /// `Parser::parse` can create one, and it only calls `ParserImpl::new` once.
279    /// This enforces the invariant throughout the entire parser.
280    ///
281    /// `UniquePromise` is a zero-sized type and has no runtime cost. It's purely for the type-checker.
282    ///
283    /// `UniquePromise::new_for_tests_and_benchmarks` is a backdoor for tests/benchmarks, so they can
284    /// create a `ParserImpl` or `Lexer`, and manipulate it directly, for testing/benchmarking purposes.
285    pub struct UniquePromise(());
286
287    impl UniquePromise {
288        #[inline]
289        fn new() -> Self {
290            Self(())
291        }
292
293        /// Backdoor for tests/benchmarks to create a `UniquePromise` (see above).
294        /// This function must NOT be exposed outside of tests and benchmarks,
295        /// as it allows circumventing safety invariants of the parser.
296        #[cfg(any(test, feature = "benchmarking"))]
297        pub fn new_for_tests_and_benchmarks() -> Self {
298            Self(())
299        }
300    }
301
302    impl<'a> Parser<'a> {
303        /// Main entry point
304        ///
305        /// Returns an empty `Program` on unrecoverable error,
306        /// Recoverable errors are stored inside `errors`.
307        ///
308        /// See the [module-level documentation](crate) for examples and more information.
309        pub fn parse(self) -> ParserReturn<'a> {
310            let unique = UniquePromise::new();
311            let parser = ParserImpl::new(
312                self.allocator,
313                self.source_text,
314                self.source_type,
315                self.options,
316                unique,
317            );
318            parser.parse()
319        }
320
321        /// Parse a single [`Expression`].
322        ///
323        /// # Example
324        ///
325        /// ```rust
326        /// use oxc_allocator::Allocator;
327        /// use oxc_ast::ast::Expression;
328        /// use oxc_parser::Parser;
329        /// use oxc_span::SourceType;
330        ///
331        /// let src = "let x = 1 + 2;";
332        /// let allocator = Allocator::new();
333        /// let source_type = SourceType::default();
334        ///
335        /// let expr: Expression<'_> = Parser::new(&allocator, src, source_type).parse_expression().unwrap();
336        /// ```
337        ///
338        /// # Errors
339        /// If the source code being parsed has syntax errors.
340        pub fn parse_expression(self) -> Result<Expression<'a>, Vec<OxcDiagnostic>> {
341            let unique = UniquePromise::new();
342            let parser = ParserImpl::new(
343                self.allocator,
344                self.source_text,
345                self.source_type,
346                self.options,
347                unique,
348            );
349            parser.parse_expression()
350        }
351    }
352}
353use parser_parse::UniquePromise;
354
355/// Implementation of parser.
356/// `Parser` is just a public wrapper, the guts of the implementation is in this type.
357struct ParserImpl<'a> {
358    options: ParseOptions,
359
360    pub(crate) lexer: Lexer<'a>,
361
362    /// SourceType: JavaScript or TypeScript, Script or Module, jsx support?
363    source_type: SourceType,
364
365    /// Source Code
366    source_text: &'a str,
367
368    /// All syntax errors from parser and lexer
369    /// Note: favor adding to `Diagnostics` instead of raising Err
370    errors: Vec<OxcDiagnostic>,
371
372    fatal_error: Option<FatalError>,
373
374    /// The current parsing token
375    token: Token,
376
377    /// The end range of the previous token
378    prev_token_end: u32,
379
380    /// Parser state
381    state: ParserState<'a>,
382
383    /// Parsing context
384    ctx: Context,
385
386    /// Ast builder for creating AST nodes
387    ast: AstBuilder<'a>,
388
389    /// Module Record Builder
390    module_record_builder: ModuleRecordBuilder<'a>,
391
392    /// Precomputed typescript detection
393    is_ts: bool,
394}
395
396impl<'a> ParserImpl<'a> {
397    /// Create a new `ParserImpl`.
398    ///
399    /// Requiring a `UniquePromise` to be provided guarantees only 1 `ParserImpl` can exist
400    /// on a single thread at one time.
401    #[inline]
402    pub fn new(
403        allocator: &'a Allocator,
404        source_text: &'a str,
405        source_type: SourceType,
406        options: ParseOptions,
407        unique: UniquePromise,
408    ) -> Self {
409        Self {
410            options,
411            lexer: Lexer::new(allocator, source_text, source_type, unique),
412            source_type,
413            source_text,
414            errors: vec![],
415            fatal_error: None,
416            token: Token::default(),
417            prev_token_end: 0,
418            state: ParserState::new(),
419            ctx: Self::default_context(source_type, options),
420            ast: AstBuilder::new(allocator),
421            module_record_builder: ModuleRecordBuilder::new(allocator),
422            is_ts: source_type.is_typescript(),
423        }
424    }
425
426    /// Main entry point
427    ///
428    /// Returns an empty `Program` on unrecoverable error,
429    /// Recoverable errors are stored inside `errors`.
430    #[inline]
431    pub fn parse(mut self) -> ParserReturn<'a> {
432        let mut program = self.parse_program();
433        let mut panicked = false;
434
435        if let Some(fatal_error) = self.fatal_error.take() {
436            panicked = true;
437            self.errors.truncate(fatal_error.errors_len);
438            if !self.lexer.errors.is_empty() && self.cur_kind().is_eof() {
439                // Noop
440            } else {
441                self.error(fatal_error.error);
442            }
443
444            program = Program::dummy(self.ast.allocator);
445            program.source_type = self.source_type;
446            program.source_text = self.source_text;
447        }
448
449        self.check_unfinished_errors();
450
451        if let Some(overlong_error) = self.overlong_error() {
452            panicked = true;
453            self.lexer.errors.clear();
454            self.errors.clear();
455            self.error(overlong_error);
456        }
457
458        let mut is_flow_language = false;
459        let mut errors = vec![];
460        // only check for `@flow` if the file failed to parse.
461        if (!self.lexer.errors.is_empty() || !self.errors.is_empty())
462            && let Some(error) = self.flow_error()
463        {
464            is_flow_language = true;
465            errors.push(error);
466        }
467        let (module_record, module_record_errors) = self.module_record_builder.build();
468        if errors.len() != 1 {
469            errors.reserve(self.lexer.errors.len() + self.errors.len());
470            errors.extend(self.lexer.errors);
471            errors.extend(self.errors);
472            // Skip checking for exports in TypeScript {
473            if !self.source_type.is_typescript() {
474                errors.extend(module_record_errors);
475            }
476        }
477        let irregular_whitespaces =
478            self.lexer.trivia_builder.irregular_whitespaces.into_boxed_slice();
479
480        let source_type = program.source_type;
481        if source_type.is_unambiguous() {
482            program.source_type = if module_record.has_module_syntax {
483                source_type.with_module(true)
484            } else {
485                source_type.with_script(true)
486            };
487        }
488
489        ParserReturn {
490            program,
491            module_record,
492            errors,
493            irregular_whitespaces,
494            panicked,
495            is_flow_language,
496        }
497    }
498
499    pub fn parse_expression(mut self) -> Result<Expression<'a>, Vec<OxcDiagnostic>> {
500        // initialize cur_token and prev_token by moving onto the first token
501        self.bump_any();
502        let expr = self.parse_expr();
503        if let Some(FatalError { error, .. }) = self.fatal_error.take() {
504            return Err(vec![error]);
505        }
506        self.check_unfinished_errors();
507        let errors = self.lexer.errors.into_iter().chain(self.errors).collect::<Vec<_>>();
508        if !errors.is_empty() {
509            return Err(errors);
510        }
511        Ok(expr)
512    }
513
514    #[expect(clippy::cast_possible_truncation)]
515    fn parse_program(&mut self) -> Program<'a> {
516        // Initialize by moving onto the first token.
517        // Checks for hashbang comment.
518        self.token = self.lexer.first_token();
519
520        let hashbang = self.parse_hashbang();
521        let (directives, statements) =
522            self.parse_directives_and_statements(/* is_top_level */ true);
523
524        let span = Span::new(0, self.source_text.len() as u32);
525        let comments = self.ast.vec_from_iter(self.lexer.trivia_builder.comments.iter().copied());
526        self.ast.program(
527            span,
528            self.source_type,
529            self.source_text,
530            comments,
531            hashbang,
532            directives,
533            statements,
534        )
535    }
536
537    fn default_context(source_type: SourceType, options: ParseOptions) -> Context {
538        let mut ctx = Context::default().and_ambient(source_type.is_typescript_definition());
539        if source_type.module_kind() == ModuleKind::Module {
540            // for [top-level-await](https://tc39.es/proposal-top-level-await/)
541            ctx = ctx.and_await(true);
542        }
543        if options.allow_return_outside_function {
544            ctx = ctx.and_return(true);
545        }
546        ctx
547    }
548
549    /// Check for Flow declaration if the file cannot be parsed.
550    /// The declaration must be [on the first line before any code](https://flow.org/en/docs/usage/#toc-prepare-your-code-for-flow)
551    fn flow_error(&mut self) -> Option<OxcDiagnostic> {
552        if !self.source_type.is_javascript() {
553            return None;
554        }
555        let span = self.lexer.trivia_builder.comments.first()?.span;
556        if span.source_text(self.source_text).contains("@flow") {
557            self.errors.clear();
558            Some(diagnostics::flow(span))
559        } else {
560            None
561        }
562    }
563
564    fn check_unfinished_errors(&mut self) {
565        use oxc_span::GetSpan;
566        // PropertyDefinition : cover_initialized_name
567        // It is a Syntax Error if any source text is matched by this production.
568        for expr in self.state.cover_initialized_name.values() {
569            self.errors.push(diagnostics::cover_initialized_name(expr.span()));
570        }
571    }
572
573    /// Check if source length exceeds MAX_LEN, if the file cannot be parsed.
574    /// Original parsing error is not real - `Lexer::new` substituted "\0" as the source text.
575    #[cold]
576    fn overlong_error(&self) -> Option<OxcDiagnostic> {
577        if self.source_text.len() > MAX_LEN {
578            return Some(diagnostics::overlong_source());
579        }
580        None
581    }
582
583    #[inline]
584    fn alloc<T>(&self, value: T) -> ArenaBox<'a, T> {
585        self.ast.alloc(value)
586    }
587}
588
589#[cfg(test)]
590mod test {
591    use std::path::Path;
592
593    use oxc_ast::ast::{CommentKind, Expression, Statement};
594    use oxc_span::GetSpan;
595
596    use super::*;
597
598    #[test]
599    fn parse_program_smoke_test() {
600        let allocator = Allocator::default();
601        let source_type = SourceType::default();
602        let source = "";
603        let ret = Parser::new(&allocator, source, source_type).parse();
604        assert!(ret.program.is_empty());
605        assert!(ret.errors.is_empty());
606        assert!(!ret.is_flow_language);
607    }
608
609    #[test]
610    fn parse_expression_smoke_test() {
611        let allocator = Allocator::default();
612        let source_type = SourceType::default();
613        let source = "a";
614        let expr = Parser::new(&allocator, source, source_type).parse_expression().unwrap();
615        assert!(matches!(expr, Expression::Identifier(_)));
616    }
617
618    #[test]
619    fn flow_error() {
620        let allocator = Allocator::default();
621        let source_type = SourceType::default();
622        let sources = [
623            "// @flow\nasdf adsf",
624            "/* @flow */\n asdf asdf",
625            "/**
626             * @flow
627             */
628             asdf asdf
629             ",
630            "/* @flow */ super;",
631        ];
632        for source in sources {
633            let ret = Parser::new(&allocator, source, source_type).parse();
634            assert!(ret.is_flow_language);
635            assert_eq!(ret.errors.len(), 1);
636            assert_eq!(ret.errors.first().unwrap().to_string(), "Flow is not supported");
637        }
638    }
639
640    #[test]
641    fn ts_module_declaration() {
642        let allocator = Allocator::default();
643        let source_type = SourceType::from_path(Path::new("module.ts")).unwrap();
644        let source = "declare module 'test'\n";
645        let ret = Parser::new(&allocator, source, source_type).parse();
646        assert_eq!(ret.errors.len(), 0);
647    }
648
649    #[test]
650    fn directives() {
651        let allocator = Allocator::default();
652        let source_type = SourceType::default();
653        let sources = [
654            ("import x from 'foo'; 'use strict';", 2),
655            ("export {x} from 'foo'; 'use strict';", 2),
656            (";'use strict';", 2),
657        ];
658        for (source, body_length) in sources {
659            let ret = Parser::new(&allocator, source, source_type).parse();
660            assert!(ret.program.directives.is_empty(), "{source}");
661            assert_eq!(ret.program.body.len(), body_length, "{source}");
662        }
663    }
664
665    #[test]
666    fn v8_intrinsics() {
667        let allocator = Allocator::default();
668        let source_type = SourceType::default();
669        {
670            let source = "%DebugPrint('Raging against the Dying Light')";
671            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
672            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
673            assert!(ret.errors.is_empty());
674
675            if let Some(Statement::ExpressionStatement(expr_stmt)) = ret.program.body.first() {
676                if let Expression::V8IntrinsicExpression(expr) = &expr_stmt.expression {
677                    assert_eq!(expr.span().source_text(source), source);
678                } else {
679                    panic!("Expected V8IntrinsicExpression");
680                }
681            } else {
682                panic!("Expected ExpressionStatement");
683            }
684        }
685        {
686            let source = "%DebugPrint(...illegalSpread)";
687            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
688            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
689            assert_eq!(ret.errors.len(), 1);
690            assert_eq!(
691                ret.errors[0].to_string(),
692                "V8 runtime calls cannot have spread elements as arguments"
693            );
694        }
695        {
696            let source = "%DebugPrint('~~')";
697            let ret = Parser::new(&allocator, source, source_type).parse();
698            assert_eq!(ret.errors.len(), 1);
699            assert_eq!(ret.errors[0].to_string(), "Unexpected token");
700        }
701        {
702            // https://github.com/oxc-project/oxc/issues/12121
703            let source = "interface Props extends %enuProps {}";
704            let source_type = SourceType::default().with_typescript(true);
705            // Should not panic whether `allow_v8_intrinsics` is set or not.
706            let opts = ParseOptions { allow_v8_intrinsics: true, ..ParseOptions::default() };
707            let ret = Parser::new(&allocator, source, source_type).with_options(opts).parse();
708            assert_eq!(ret.errors.len(), 1);
709            let ret = Parser::new(&allocator, source, source_type).parse();
710            assert_eq!(ret.errors.len(), 1);
711        }
712    }
713
714    #[test]
715    fn comments() {
716        let allocator = Allocator::default();
717        let source_type = SourceType::default().with_typescript(true);
718        let sources = [
719            ("// line comment", CommentKind::Line),
720            ("/* line comment */", CommentKind::Block),
721            (
722                "type Foo = ( /* Require properties which are not generated automatically. */ 'bar')",
723                CommentKind::Block,
724            ),
725        ];
726        for (source, kind) in sources {
727            let ret = Parser::new(&allocator, source, source_type).parse();
728            let comments = &ret.program.comments;
729            assert_eq!(comments.len(), 1, "{source}");
730            assert_eq!(comments.first().unwrap().kind, kind, "{source}");
731        }
732    }
733
734    #[test]
735    fn hashbang() {
736        let allocator = Allocator::default();
737        let source_type = SourceType::default();
738        let source = "#!/usr/bin/node\n;";
739        let ret = Parser::new(&allocator, source, source_type).parse();
740        assert_eq!(ret.program.hashbang.unwrap().value.as_str(), "/usr/bin/node");
741    }
742
743    #[test]
744    fn unambiguous() {
745        let allocator = Allocator::default();
746        let source_type = SourceType::unambiguous();
747        assert!(source_type.is_unambiguous());
748        let sources = ["import x from 'foo';", "export {x} from 'foo';", "import.meta"];
749        for source in sources {
750            let ret = Parser::new(&allocator, source, source_type).parse();
751            assert!(ret.program.source_type.is_module());
752        }
753
754        let sources = ["", "import('foo')"];
755        for source in sources {
756            let ret = Parser::new(&allocator, source, source_type).parse();
757            assert!(ret.program.source_type.is_script());
758        }
759    }
760
761    #[test]
762    fn memory_leak() {
763        let allocator = Allocator::default();
764        let source_type = SourceType::default();
765        let sources = ["2n", ";'1234567890123456789012345678901234567890'"];
766        for source in sources {
767            let ret = Parser::new(&allocator, source, source_type).parse();
768            assert!(!ret.program.body.is_empty());
769        }
770    }
771
772    // Source with length MAX_LEN + 1 fails to parse.
773    // Skip this test on 32-bit systems as impossible to allocate a string longer than `isize::MAX`.
774    // Also skip running under Miri since it takes so long.
775    #[cfg(target_pointer_width = "64")]
776    #[cfg(not(miri))]
777    #[test]
778    fn overlong_source() {
779        // Build string in 16 KiB chunks for speed
780        let mut source = String::with_capacity(MAX_LEN + 1);
781        let line = "var x = 123456;\n";
782        let chunk = line.repeat(1024);
783        while source.len() < MAX_LEN + 1 - chunk.len() {
784            source.push_str(&chunk);
785        }
786        while source.len() < MAX_LEN + 1 - line.len() {
787            source.push_str(line);
788        }
789        while source.len() < MAX_LEN + 1 {
790            source.push('\n');
791        }
792        assert_eq!(source.len(), MAX_LEN + 1);
793
794        let allocator = Allocator::default();
795        let ret = Parser::new(&allocator, &source, SourceType::default()).parse();
796        assert!(ret.program.is_empty());
797        assert!(ret.panicked);
798        assert_eq!(ret.errors.len(), 1);
799        assert_eq!(ret.errors.first().unwrap().to_string(), "Source length exceeds 4 GiB limit");
800    }
801
802    // Source with length MAX_LEN parses OK.
803    // This test takes over 1 minute on an M1 Macbook Pro unless compiled in release mode.
804    // `not(debug_assertions)` is a proxy for detecting release mode.
805    // Also skip running under Miri since it takes so long.
806    #[cfg(not(debug_assertions))]
807    #[cfg(not(miri))]
808    #[test]
809    fn legal_length_source() {
810        // Build a string MAX_LEN bytes long which doesn't take too long to parse
811        let head = "const x = 1;\n/*";
812        let foot = "*/\nconst y = 2;\n";
813        let mut source = "x".repeat(MAX_LEN);
814        source.replace_range(..head.len(), head);
815        source.replace_range(MAX_LEN - foot.len().., foot);
816        assert_eq!(source.len(), MAX_LEN);
817
818        let allocator = Allocator::default();
819        let ret = Parser::new(&allocator, &source, SourceType::default()).parse();
820        assert!(!ret.panicked);
821        assert!(ret.errors.is_empty());
822        assert_eq!(ret.program.body.len(), 2);
823    }
824}