1#![doc = include_str!("../examples/parser.rs")]
41#![doc = include_str!("../examples/parser_tsx.rs")]
46#![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#[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
129pub(crate) const MAX_LEN: usize = if size_of::<usize>() >= 8 {
136 u32::MAX as usize
138} else {
139 isize::MAX as usize
141};
142
143#[non_exhaustive]
167pub struct ParserReturn<'a> {
168 pub program: Program<'a>,
180
181 pub module_record: ModuleRecord<'a>,
183
184 pub diagnostics: Diagnostics,
190
191 pub irregular_whitespaces: Box<[Span]>,
193
194 pub tokens: ArenaVec<'a, Token>,
198
199 pub panicked: bool,
208
209 pub is_flow_language: bool,
211
212 pub(crate) has_legacy_literals: bool,
214 pub(crate) has_jsdoc_comments: bool,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub struct ParseOptions {
223 #[cfg(feature = "regular_expression")]
227 pub parse_regular_expression: bool,
228
229 pub allow_return_outside_function: bool,
237
238 pub preserve_parens: bool,
249
250 pub allow_v8_intrinsics: bool,
257
258 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
285pub 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 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 #[must_use]
312 pub fn with_options(mut self, options: ParseOptions) -> Self {
313 self.options = options;
314 self
315 }
316
317 #[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 pub struct UniquePromise(());
352
353 impl UniquePromise {
354 #[inline]
355 fn new() -> Self {
356 Self(())
357 }
358
359 #[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 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 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 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 #[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 #[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
645struct ParserImpl<'a, C: ParserConfig> {
648 options: ParseOptions,
650
651 pub(crate) lexer: Lexer<'a, C::LexerConfig>,
652
653 source_type: SourceType,
655
656 source_text: &'a str,
658
659 errors: Vec<ParserDiagnostic<'a>>,
664
665 deferred_script_errors: Vec<ParserDiagnostic<'a>>,
671
672 fatal_error: Option<FatalError<'a>>,
673
674 token: Token,
676
677 prev_token_end: u32,
679
680 state: ParserState<'a>,
682
683 ctx: Context,
685
686 ast: AstBuilder<'a>,
688
689 module_record_builder: ModuleRecordBuilder<'a>,
691
692 is_ts: bool,
694
695 markup: Option<&'a dyn MarkupGrammar<'a>>,
697}
698
699impl<'a, C: ParserConfig> ParserImpl<'a, C> {
700 #[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 #[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 } 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 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 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 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 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 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(false);
856
857 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 [],
875 hashbang,
876 directives,
877 statements,
878 self,
879 )
880 }
881
882 fn reparse_potential_top_level_awaits(&mut self, statements: &mut ArenaVec<'a, Statement<'a>>) {
888 let original_tokens =
891 if self.lexer.config.tokens() { Some(self.lexer.take_tokens()) } else { None };
892
893 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 self.rewind(checkpoint);
901
902 let stmt = self.context_add(Context::Await, |p| {
904 p.parse_statement_list_item(StatementContext::StatementList)
905 });
906
907 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 ctx = ctx.and_await(true);
925 }
926 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 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 for expr in self.state.cover_initialized_name.values() {
957 self.errors.push(diagnostics::cover_initialized_name(expr.span()));
958 }
959 }
960
961 #[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 let source = "interface Props extends %enuProps {}";
1108 let source_type = SourceType::default().with_typescript(true);
1109 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 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 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 #[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 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 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 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 unsafe { alloc::dealloc(self.ptr.as_ptr(), Self::LAYOUT) };
1245 }
1246 }
1247
1248 let zeroed_string = ZeroedString::new();
1250 let source_text = zeroed_string.as_str();
1251
1252 let allocator = Allocator::default();
1254 let ret = Parser::new(&allocator, source_text, SourceType::default()).parse();
1255
1256 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 #[cfg(not(debug_assertions))]
1271 #[cfg(not(miri))]
1272 #[test]
1273 fn legal_length_source() {
1274 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}