1use crate::engine::value::{JsString as RuntimeJsString, JsStringError, decode_quickjs_utf8};
25use crate::source::text::{INVALID_BYTE_CARRIER, SourceText};
26use std::fmt;
27use std::iter::FusedIterator;
28use std::ops::Range;
29
30const TEMPLATE_QUOTE: char = '\u{0060}';
31
32pub use crate::source::{SourceLocation as Position, SourceSpan as Span};
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
39pub struct LexContext {
40 pub strict: bool,
41 pub module: bool,
42 pub generator: bool,
43 pub async_function: bool,
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub struct LexerOptions {
49 pub context: LexContext,
50 pub allow_html_comments: bool,
52}
53
54#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
56pub enum LexicalGoal {
57 #[default]
59 Div,
60 RegExp,
62 TemplateContinuation,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
67pub enum Keyword {
68 Null,
69 False,
70 True,
71 If,
72 Else,
73 Return,
74 Var,
75 This,
76 Delete,
77 Void,
78 Typeof,
79 New,
80 In,
81 Instanceof,
82 Do,
83 While,
84 For,
85 Break,
86 Continue,
87 Switch,
88 Case,
89 Default,
90 Throw,
91 Try,
92 Catch,
93 Finally,
94 Function,
95 Debugger,
96 With,
97 Class,
98 Const,
99 Enum,
100 Export,
101 Extends,
102 Import,
103 Super,
104 Implements,
105 Interface,
106 Let,
107 Package,
108 Private,
109 Protected,
110 Public,
111 Static,
112 Yield,
113 Await,
114}
115
116impl Keyword {
117 pub const fn as_str(self) -> &'static str {
118 match self {
119 Self::Null => "null",
120 Self::False => "false",
121 Self::True => "true",
122 Self::If => "if",
123 Self::Else => "else",
124 Self::Return => "return",
125 Self::Var => "var",
126 Self::This => "this",
127 Self::Delete => "delete",
128 Self::Void => "void",
129 Self::Typeof => "typeof",
130 Self::New => "new",
131 Self::In => "in",
132 Self::Instanceof => "instanceof",
133 Self::Do => "do",
134 Self::While => "while",
135 Self::For => "for",
136 Self::Break => "break",
137 Self::Continue => "continue",
138 Self::Switch => "switch",
139 Self::Case => "case",
140 Self::Default => "default",
141 Self::Throw => "throw",
142 Self::Try => "try",
143 Self::Catch => "catch",
144 Self::Finally => "finally",
145 Self::Function => "function",
146 Self::Debugger => "debugger",
147 Self::With => "with",
148 Self::Class => "class",
149 Self::Const => "const",
150 Self::Enum => "enum",
151 Self::Export => "export",
152 Self::Extends => "extends",
153 Self::Import => "import",
154 Self::Super => "super",
155 Self::Implements => "implements",
156 Self::Interface => "interface",
157 Self::Let => "let",
158 Self::Package => "package",
159 Self::Private => "private",
160 Self::Protected => "protected",
161 Self::Public => "public",
162 Self::Static => "static",
163 Self::Yield => "yield",
164 Self::Await => "await",
165 }
166 }
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
170pub enum Punctuator {
171 LeftBrace,
172 RightBrace,
173 LeftParen,
174 RightParen,
175 LeftBracket,
176 RightBracket,
177 Dot,
178 Ellipsis,
179 Semicolon,
180 Comma,
181 Less,
182 LessEqual,
183 Greater,
184 GreaterEqual,
185 Equal,
186 EqualEqual,
187 StrictEqual,
188 Not,
189 NotEqual,
190 StrictNotEqual,
191 Plus,
192 Minus,
193 Multiply,
194 Divide,
195 Remainder,
196 Exponent,
197 Increment,
198 Decrement,
199 ShiftLeft,
200 ShiftRight,
201 UnsignedShiftRight,
202 BitAnd,
203 BitOr,
204 BitXor,
205 BitNot,
206 LogicalAnd,
207 LogicalOr,
208 NullishCoalesce,
209 Question,
210 OptionalChain,
211 Colon,
212 Arrow,
213 PlusAssign,
214 MinusAssign,
215 MultiplyAssign,
216 DivideAssign,
217 RemainderAssign,
218 ExponentAssign,
219 ShiftLeftAssign,
220 ShiftRightAssign,
221 UnsignedShiftRightAssign,
222 BitAndAssign,
223 BitOrAssign,
224 BitXorAssign,
225 LogicalAndAssign,
226 LogicalOrAssign,
227 NullishAssign,
228}
229
230impl Punctuator {
231 pub const fn as_str(self) -> &'static str {
232 match self {
233 Self::LeftBrace => "{",
234 Self::RightBrace => "}",
235 Self::LeftParen => "(",
236 Self::RightParen => ")",
237 Self::LeftBracket => "[",
238 Self::RightBracket => "]",
239 Self::Dot => ".",
240 Self::Ellipsis => "...",
241 Self::Semicolon => ";",
242 Self::Comma => ",",
243 Self::Less => "<",
244 Self::LessEqual => "<=",
245 Self::Greater => ">",
246 Self::GreaterEqual => ">=",
247 Self::Equal => "=",
248 Self::EqualEqual => "==",
249 Self::StrictEqual => "===",
250 Self::Not => "!",
251 Self::NotEqual => "!=",
252 Self::StrictNotEqual => "!==",
253 Self::Plus => "+",
254 Self::Minus => "-",
255 Self::Multiply => "*",
256 Self::Divide => "/",
257 Self::Remainder => "%",
258 Self::Exponent => "**",
259 Self::Increment => "++",
260 Self::Decrement => "--",
261 Self::ShiftLeft => "<<",
262 Self::ShiftRight => ">>",
263 Self::UnsignedShiftRight => ">>>",
264 Self::BitAnd => "&",
265 Self::BitOr => "|",
266 Self::BitXor => "^",
267 Self::BitNot => "~",
268 Self::LogicalAnd => "&&",
269 Self::LogicalOr => "||",
270 Self::NullishCoalesce => "??",
271 Self::Question => "?",
272 Self::OptionalChain => "?.",
273 Self::Colon => ":",
274 Self::Arrow => "=>",
275 Self::PlusAssign => "+=",
276 Self::MinusAssign => "-=",
277 Self::MultiplyAssign => "*=",
278 Self::DivideAssign => "/=",
279 Self::RemainderAssign => "%=",
280 Self::ExponentAssign => "**=",
281 Self::ShiftLeftAssign => "<<=",
282 Self::ShiftRightAssign => ">>=",
283 Self::UnsignedShiftRightAssign => ">>>=",
284 Self::BitAndAssign => "&=",
285 Self::BitOrAssign => "|=",
286 Self::BitXorAssign => "^=",
287 Self::LogicalAndAssign => "&&=",
288 Self::LogicalOrAssign => "||=",
289 Self::NullishAssign => "??=",
290 }
291 }
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
295pub enum NumericRadix {
296 Binary,
297 Octal,
298 Decimal,
299 Hexadecimal,
300}
301
302#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
303pub enum NumberKind {
304 Integer(NumericRadix),
305 Float,
306 BigInt(NumericRadix),
307 LegacyOctal,
309 LegacyDecimal,
311}
312
313#[derive(Clone, Copy, Debug, PartialEq, Eq)]
314pub struct NumberLiteral<'a> {
315 pub raw: &'a str,
316 pub kind: NumberKind,
317}
318
319#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
324pub struct JsString {
325 pub utf16: Vec<u16>,
326}
327
328impl JsString {
329 #[cfg(test)]
330 pub fn to_string(&self) -> Result<String, std::string::FromUtf16Error> {
331 String::from_utf16(&self.utf16)
332 }
333}
334
335trait StringSink {
339 fn push_code_unit(&mut self, unit: u16) -> Result<(), JsStringError>;
340
341 fn push_ascii(&mut self, bytes: &[u8]) -> Result<(), usize>;
345
346 fn push_char(&mut self, ch: char) -> Result<(), JsStringError> {
347 self.push_code_point(ch as u32)
348 }
349
350 fn push_code_point(&mut self, value: u32) -> Result<(), JsStringError> {
351 if value <= 0xffff {
352 self.push_code_unit(value as u16)
353 } else {
354 let adjusted = value - 0x1_0000;
355 self.push_code_unit(0xd800 | ((adjusted >> 10) as u16))?;
356 self.push_code_unit(0xdc00 | ((adjusted & 0x3ff) as u16))
357 }
358 }
359}
360
361struct Utf16Sink {
362 units: Vec<u16>,
363 limit: usize,
364}
365
366impl Utf16Sink {
367 fn new(limit: usize) -> Self {
368 Self {
369 units: Vec::new(),
370 limit,
371 }
372 }
373
374 fn into_js_string(self) -> JsString {
375 JsString { utf16: self.units }
376 }
377}
378
379impl StringSink for Utf16Sink {
380 fn push_ascii(&mut self, bytes: &[u8]) -> Result<(), usize> {
381 let remaining = self.limit.saturating_sub(self.units.len());
382 if bytes.len() > remaining {
383 return Err(remaining + 1);
384 }
385 self.units.extend(bytes.iter().copied().map(u16::from));
386 Ok(())
387 }
388
389 fn push_code_unit(&mut self, unit: u16) -> Result<(), JsStringError> {
390 RuntimeJsString::checked_length_with_limit(self.units.len(), 1, self.limit)?;
391 self.units.push(unit);
392 Ok(())
393 }
394}
395
396struct ValidateSink {
399 len: usize,
400 limit: usize,
401}
402
403impl ValidateSink {
404 fn new(limit: usize) -> Self {
405 Self { len: 0, limit }
406 }
407}
408
409impl StringSink for ValidateSink {
410 fn push_ascii(&mut self, bytes: &[u8]) -> Result<(), usize> {
411 let remaining = self.limit.saturating_sub(self.len);
412 if bytes.len() > remaining {
413 return Err(remaining + 1);
414 }
415 self.len += bytes.len();
416 Ok(())
417 }
418
419 fn push_code_unit(&mut self, _unit: u16) -> Result<(), JsStringError> {
420 RuntimeJsString::checked_length_with_limit(self.len, 1, self.limit)?;
421 self.len += 1;
422 Ok(())
423 }
424}
425
426#[derive(Clone, Copy, Debug, PartialEq, Eq)]
427pub struct StringLiteral<'a> {
428 pub raw: &'a str,
429 pub has_escape: bool,
430 pub has_legacy_octal_escape: bool,
431}
432
433#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
434pub enum TemplatePartKind {
435 NoSubstitution,
436 Head,
437 Middle,
438 Tail,
439}
440
441#[derive(Clone, Copy, Debug, PartialEq, Eq)]
442pub struct TemplateEscapeError {
443 pub message: &'static str,
444 pub span: Span,
445}
446
447#[derive(Clone, Copy, Debug, PartialEq, Eq)]
448pub struct TemplatePart<'a> {
449 pub raw: &'a str,
451 pub invalid_escape: bool,
455 pub kind: TemplatePartKind,
456}
457
458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459pub struct RegExpLiteral<'a> {
460 pub pattern: &'a str,
461 pub flags: &'a str,
462}
463
464#[derive(Clone, Copy, Debug, PartialEq, Eq)]
465pub struct Identifier<'a> {
466 pub raw: &'a str,
467 pub has_escape: bool,
468 pub keyword_hint: Option<Keyword>,
470 pub escaped_reserved_word: bool,
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
476pub enum TokenKind<'a> {
477 Identifier(Identifier<'a>),
478 PrivateIdentifier(Identifier<'a>),
479 Keyword(Keyword),
480 Number(NumberLiteral<'a>),
481 String(StringLiteral<'a>),
482 Template(TemplatePart<'a>),
483 RegExp(RegExpLiteral<'a>),
484 Punctuator(Punctuator),
485 RawAscii(u8),
489 Eof,
490}
491
492#[derive(Clone, Copy, Debug, PartialEq, Eq)]
493pub struct Token<'a> {
494 pub kind: TokenKind<'a>,
495 pub span: Span,
496 pub line_terminator_before: bool,
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
502pub enum LexErrorKind {
503 UnexpectedCharacter,
504 UnterminatedComment,
505 UnterminatedString,
506 UnterminatedTemplate,
507 InvalidEscape,
508 InvalidNumber,
509 InvalidPrivateIdentifier,
510 UnterminatedRegExp,
511 LineTerminatorInRegExp,
512 ExpectedRegExp,
513 ExpectedTemplateContinuation,
514 StringTooLong,
515}
516
517#[derive(Clone, Copy, Debug, PartialEq, Eq)]
518pub struct LexError {
519 pub kind: LexErrorKind,
520 pub span: Span,
521 pub message: &'static str,
522}
523
524impl fmt::Display for LexError {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 write!(
527 f,
528 "{}:{} (byte {}): {}",
529 self.span.start.line, self.span.start.column, self.span.start.byte_offset, self.message
530 )
531 }
532}
533
534impl std::error::Error for LexError {}
535
536#[derive(Clone)]
538pub struct Lexer<'a> {
539 source: &'a str,
540 source_text: Option<&'a SourceText>,
541 offset: usize,
542 line: u32,
543 column: u32,
544 options: LexerOptions,
545 eof_emitted: bool,
546 failed: bool,
547 string_limit: usize,
548}
549
550impl<'a> Lexer<'a> {
551 #[cfg(any(test, feature = "test-support"))]
552 pub fn new(source: &'a str) -> Self {
553 Self::with_options(source, LexerOptions::default())
554 }
555
556 pub fn with_options(source: &'a str, options: LexerOptions) -> Self {
557 Self {
558 source,
559 source_text: None,
560 offset: 0,
561 line: 1,
562 column: 1,
563 options,
564 eof_emitted: false,
565 failed: false,
566 string_limit: RuntimeJsString::MAX_LEN,
567 }
568 }
569
570 pub fn with_source_text(source: &'a SourceText, options: LexerOptions) -> Self {
576 Self {
577 source: source.carrier(),
578 source_text: Some(source),
579 offset: 0,
580 line: 1,
581 column: 1,
582 options,
583 eof_emitted: false,
584 failed: false,
585 string_limit: RuntimeJsString::MAX_LEN,
586 }
587 }
588
589 #[cfg(test)]
590 fn with_string_limit(mut self, limit: usize) -> Self {
591 self.string_limit = limit.min(RuntimeJsString::MAX_LEN);
592 self
593 }
594
595 pub fn source(&self) -> &'a str {
596 self.source
597 }
598
599 pub fn current_position(&self) -> Position {
600 Position::new(self.offset, self.line, self.column)
601 }
602
603 pub fn seek(&mut self, position: Position) {
607 debug_assert!(position.byte_offset <= self.source.len());
608 debug_assert!(self.source.is_char_boundary(position.byte_offset));
609 self.offset = position.byte_offset;
610 self.line = position.line;
611 self.column = position.column;
612 self.eof_emitted = false;
613 self.failed = false;
614 }
615
616 pub fn context(&self) -> LexContext {
617 self.options.context
618 }
619
620 pub fn set_context(&mut self, context: LexContext) {
622 self.options.context = context;
623 }
624
625 #[cfg(test)]
631 pub fn tokenize(mut self) -> Result<Vec<Token<'a>>, LexError> {
632 let mut tokens = Vec::new();
633 loop {
634 let token = self.next_token()?;
635 let is_eof = matches!(token.kind, TokenKind::Eof);
636 tokens.push(token);
637 if is_eof {
638 return Ok(tokens);
639 }
640 }
641 }
642
643 pub fn next_token(&mut self) -> Result<Token<'a>, LexError> {
644 self.next_token_with_goal(LexicalGoal::Div)
645 }
646
647 pub fn next_token_with_goal(&mut self, goal: LexicalGoal) -> Result<Token<'a>, LexError> {
648 let position = self.current_position();
649 let mut token = Token {
650 kind: TokenKind::Eof,
651 span: Span::new(position, position),
652 line_terminator_before: false,
653 };
654 self.next_token_into(goal, &mut token)?;
655 Ok(token)
656 }
657
658 pub fn next_token_into(
662 &mut self,
663 goal: LexicalGoal,
664 token: &mut Token<'a>,
665 ) -> Result<(), LexError> {
666 if goal == LexicalGoal::TemplateContinuation {
667 *token = self.scan_template(false, false)?;
668 return Ok(());
669 }
670
671 let line_terminator_before = match self.source.as_bytes().get(self.offset) {
675 Some(&byte) if byte >= 0x80 || ASCII_FLAGS[byte as usize] & MAY_START_TRIVIA != 0 => {
676 self.skip_trivia()?
677 }
678 _ => false,
679 };
680 let start = self.current_position();
681
682 if self.offset == self.source.len() {
683 self.eof_emitted = true;
684 *token = Token {
685 kind: TokenKind::Eof,
686 span: Span::new(start, start),
687 line_terminator_before,
688 };
689 return Ok(());
690 }
691
692 if self.invalid_source_byte_at(self.offset) {
693 return Err(self.invalid_utf8_error_here("unexpected character"));
694 }
695
696 if goal == LexicalGoal::RegExp {
697 if self.peek_char() == Some('/') {
698 *token = self.scan_regexp(start, line_terminator_before)?;
699 return Ok(());
700 }
701 return Err(self.error_here(
702 LexErrorKind::ExpectedRegExp,
703 "regular-expression lexical goal requires a slash",
704 ));
705 }
706
707 let byte = self.source.as_bytes()[self.offset];
708 let dispatch = ASCII_DISPATCH
709 .get(byte as usize)
710 .copied()
711 .unwrap_or(ScanKind::Slow);
712 let kind = match dispatch {
713 ScanKind::Single(punctuator) => {
714 self.offset += 1;
715 self.column = self.column.saturating_add(1);
716 TokenKind::Punctuator(punctuator)
717 }
718 ScanKind::Identifier => self.scan_identifier(false)?,
719 ScanKind::Number => self.scan_number(false)?,
720 ScanKind::String => self.scan_string()?,
721 ScanKind::Template => {
722 *token = self.scan_template(true, line_terminator_before)?;
723 return Ok(());
724 }
725 ScanKind::Dot
726 if self
727 .source
728 .as_bytes()
729 .get(self.offset + 1)
730 .is_some_and(u8::is_ascii_digit) =>
731 {
732 self.scan_number(true)?
733 }
734 ScanKind::Private => self.scan_identifier(true)?,
735 ScanKind::Slow => {
736 let ch = self.peek_char().expect("checked non-empty source");
737 if is_identifier_start(ch) || ch == '\\' {
738 self.scan_identifier(false)?
739 } else {
740 self.scan_punctuator()?
741 }
742 }
743 ScanKind::Dot | ScanKind::Punctuator => self.scan_punctuator()?,
744 };
745
746 *token = Token {
747 kind,
748 span: Span::new(start, self.current_position()),
749 line_terminator_before,
750 };
751 Ok(())
752 }
753
754 fn peek_char(&self) -> Option<char> {
755 let byte = *self.source.as_bytes().get(self.offset)?;
756 if byte < 0x80 {
757 return Some(byte as char);
758 }
759 self.source.get(self.offset..)?.chars().next()
760 }
761
762 fn peek_nth_char(&self, n: usize) -> Option<char> {
763 self.source.get(self.offset..)?.chars().nth(n)
764 }
765
766 fn starts_with(&self, text: &str) -> bool {
767 self.source
768 .get(self.offset..)
769 .is_some_and(|rest| rest.starts_with(text))
770 }
771
772 fn surrogate_at(&self, byte_offset: usize) -> Option<u16> {
773 self.source_text
774 .and_then(|source| source.surrogate_at(byte_offset))
775 }
776
777 fn invalid_source_byte_at(&self, byte_offset: usize) -> bool {
778 self.source_text
779 .is_some_and(|source| source.invalid_byte_at(byte_offset))
780 }
781
782 fn invalid_utf8_error_here(&self, message: &'static str) -> LexError {
783 self.error_here(LexErrorKind::UnexpectedCharacter, message)
784 }
785
786 fn semantic_utf16_units(&self, range: Range<usize>) -> Option<Vec<u16>> {
787 if let Some(source) = self.source_text {
788 return source
789 .slice(range)
790 .and_then(|slice| slice.semantic_utf16_units())
791 .map(Iterator::collect);
792 }
793 self.source
794 .get(range)
795 .map(|source| source.encode_utf16().collect())
796 }
797
798 pub fn source_range_to_js_string(
799 &self,
800 range: Range<usize>,
801 ) -> Result<Option<RuntimeJsString>, JsStringError> {
802 if let Some(source) = self.source_text {
803 return source.try_range_to_js_string(range);
804 }
805 self.source
806 .get(range)
807 .map(RuntimeJsString::try_from_utf8)
808 .transpose()
809 }
810
811 fn bump_char(&mut self) -> Option<char> {
813 let byte = *self.source.as_bytes().get(self.offset)?;
814 if byte < 0x80 && byte != INVALID_BYTE_CARRIER as u8 {
818 self.offset += 1;
819 return Some(match byte {
820 b'\r' => {
821 if self.source.as_bytes().get(self.offset) == Some(&b'\n') {
822 self.offset += 1;
823 }
824 self.line = self.line.saturating_add(1);
825 self.column = 1;
826 '\r'
827 }
828 b'\n' => {
829 self.line = self.line.saturating_add(1);
830 self.column = 1;
831 '\n'
832 }
833 _ => {
834 self.column = self.column.saturating_add(1);
835 byte as char
836 }
837 });
838 }
839
840 let ch = self.source.get(self.offset..)?.chars().next()?;
841 let byte_len = ch.len_utf8();
842 let column_delta = self.quickjs_column_delta(self.offset, byte_len);
843 self.offset += byte_len;
844 if is_line_terminator(ch) {
845 self.line = self.line.saturating_add(1);
846 self.column = 1;
847 } else {
848 self.column = self.column.saturating_add(column_delta);
849 }
850 Some(ch)
851 }
852
853 fn quickjs_column_delta(&self, byte_offset: usize, byte_len: usize) -> u32 {
854 let Some(raw) = self.source_text.and_then(|source| {
855 source
856 .raw_bytes()
857 .get(byte_offset..byte_offset.saturating_add(byte_len))
858 }) else {
859 return 1;
860 };
861 raw.iter()
862 .copied()
863 .filter(|byte| !(0x80..=0xbf).contains(byte))
864 .count() as u32
865 }
866
867 fn error_from(&self, start: Position, kind: LexErrorKind, message: &'static str) -> LexError {
868 LexError {
869 kind,
870 span: Span::new(start, self.current_position()),
871 message,
872 }
873 }
874
875 fn error_here(&self, kind: LexErrorKind, message: &'static str) -> LexError {
876 let start = self.current_position();
877 let mut end = start;
878 if let Some(ch) = self.peek_char() {
879 let byte_len = if ch == '\r' && self.starts_with("\r\n") {
880 2
881 } else {
882 ch.len_utf8()
883 };
884 end.byte_offset += byte_len;
885 if is_line_terminator(ch) {
886 end.line = end.line.saturating_add(1);
887 end.column = 1;
888 } else {
889 end.column = end
890 .column
891 .saturating_add(self.quickjs_column_delta(self.offset, byte_len));
892 }
893 }
894 LexError {
895 kind,
896 span: Span::new(start, end),
897 message,
898 }
899 }
900
901 fn string_too_long(&self, start: Position) -> LexError {
902 self.error_from(start, LexErrorKind::StringTooLong, "string too long")
903 }
904
905 fn skip_trivia(&mut self) -> Result<bool, LexError> {
906 let mut saw_line_terminator = false;
907 let token_search_start = self.offset;
908
909 loop {
910 let Some(&byte) = self.source.as_bytes().get(self.offset) else {
911 return Ok(saw_line_terminator);
912 };
913
914 if byte >= 0x80 {
915 let ch = self.peek_char().expect("checked non-empty source");
916 if is_line_terminator(ch) {
917 self.bump_char();
918 saw_line_terminator = true;
919 continue;
920 }
921 if is_js_whitespace(ch) {
922 self.bump_char();
923 continue;
924 }
925 return Ok(saw_line_terminator);
926 }
927
928 match byte {
929 b' ' | b'\t' | 0x0b | 0x0c => {
930 let bytes = self.source.as_bytes();
931 let mut end = self.offset + 1;
932 while let Some(&next) = bytes.get(end) {
933 if matches!(next, b' ' | b'\t' | 0x0b | 0x0c) {
934 end += 1;
935 } else {
936 break;
937 }
938 }
939 self.column = self.column.saturating_add((end - self.offset) as u32);
940 self.offset = end;
941 }
942 b'\n' => {
943 self.offset += 1;
944 self.line = self.line.saturating_add(1);
945 self.column = 1;
946 saw_line_terminator = true;
947 }
948 b'\r' => {
949 self.offset += 1;
950 if self.source.as_bytes().get(self.offset) == Some(&b'\n') {
951 self.offset += 1;
952 }
953 self.line = self.line.saturating_add(1);
954 self.column = 1;
955 saw_line_terminator = true;
956 }
957 b'#' if self.offset == 0 && self.source.as_bytes().get(1) == Some(&b'!') => {
958 self.offset = 2;
959 self.column = self.column.saturating_add(2);
960 self.skip_to_line_end();
961 }
962 b'/' => match self.source.as_bytes().get(self.offset + 1) {
963 Some(b'/') => {
964 self.offset += 2;
965 self.column = self.column.saturating_add(2);
966 self.skip_to_line_end();
967 }
968 Some(b'*') => {
969 let comment_start = self.current_position();
970 self.offset += 2;
971 self.column = self.column.saturating_add(2);
972 let bytes = self.source.as_bytes();
973 let mut terminated = false;
974 while self.offset < bytes.len() {
975 if bytes[self.offset] == b'*'
976 && bytes.get(self.offset + 1) == Some(&b'/')
977 {
978 self.offset += 2;
979 self.column = self.column.saturating_add(2);
980 terminated = true;
981 break;
982 }
983 let next = self.peek_char().expect("not at end");
984 if is_line_terminator(next) {
985 saw_line_terminator = true;
986 }
987 self.bump_char();
988 }
989 if !terminated {
990 return Err(self.error_from(
991 comment_start,
992 LexErrorKind::UnterminatedComment,
993 "unexpected end of comment",
994 ));
995 }
996 }
997 _ => return Ok(saw_line_terminator),
998 },
999 b'<' if self.options.allow_html_comments
1000 && self.source.as_bytes().get(self.offset + 1) == Some(&b'!')
1001 && self.source.as_bytes().get(self.offset + 2) == Some(&b'-')
1002 && self.source.as_bytes().get(self.offset + 3) == Some(&b'-') =>
1003 {
1004 self.offset += 4;
1005 self.column = self.column.saturating_add(4);
1006 self.skip_to_line_end();
1007 }
1008 b'-' if self.options.allow_html_comments
1009 && (saw_line_terminator || token_search_start == 0)
1010 && self.source.as_bytes().get(self.offset + 1) == Some(&b'-')
1011 && self.source.as_bytes().get(self.offset + 2) == Some(&b'>') =>
1012 {
1013 self.offset += 3;
1014 self.column = self.column.saturating_add(3);
1015 self.skip_to_line_end();
1016 }
1017 _ => return Ok(saw_line_terminator),
1018 }
1019 }
1020 }
1021
1022 fn skip_to_line_end(&mut self) {
1023 loop {
1024 let Some(&byte) = self.source.as_bytes().get(self.offset) else {
1025 return;
1026 };
1027 if byte < 0x80 && byte != b'\n' && byte != b'\r' && byte != INVALID_BYTE_CARRIER as u8 {
1028 let bytes = self.source.as_bytes();
1029 let mut end = self.offset + 1;
1030 while let Some(&next) = bytes.get(end) {
1031 if next < 0x80
1032 && next != b'\n'
1033 && next != b'\r'
1034 && next != INVALID_BYTE_CARRIER as u8
1035 {
1036 end += 1;
1037 } else {
1038 break;
1039 }
1040 }
1041 self.column = self.column.saturating_add((end - self.offset) as u32);
1042 self.offset = end;
1043 continue;
1044 }
1045 let Some(ch) = self.peek_char() else {
1046 return;
1047 };
1048 if is_line_terminator(ch) {
1049 return;
1050 }
1051 self.bump_char();
1052 }
1053 }
1054
1055 fn scan_identifier(&mut self, private: bool) -> Result<TokenKind<'a>, LexError> {
1056 let raw_start = self.offset;
1057 let value_start = raw_start + usize::from(private);
1058 let bytes = self.source.as_bytes();
1059 if bytes
1060 .get(value_start)
1061 .is_some_and(|byte| *byte < 0x80 && is_ascii_identifier_start_byte(*byte))
1062 {
1063 let mut end = value_start;
1064 let mut flags = 0;
1065 while let Some(&byte) = bytes.get(end) {
1066 if byte >= 0x80 || !is_ascii_identifier_continue_byte(byte) {
1067 break;
1068 }
1069 flags |= ASCII_FLAGS[byte as usize];
1070 end += 1;
1071 }
1072 if !matches!(bytes.get(end), Some(b'\\' | 0x80..=0xff)) {
1075 let length = end - raw_start;
1076 if length > self.string_limit {
1077 let start = self.current_position();
1078 self.offset += self.string_limit + 1;
1079 self.column = self.column.saturating_add((self.string_limit + 1) as u32);
1080 return Err(self.string_too_long(start));
1081 }
1082 self.offset = end;
1083 self.column = self.column.saturating_add(length as u32);
1084 let raw = &self.source[raw_start..end];
1085 let keyword_hint = if flags & NOT_KEYWORD == 0 {
1086 keyword_from_str(&self.source[value_start..end])
1087 } else {
1088 None
1089 };
1090 if !private
1091 && let Some(keyword) =
1092 keyword_hint.filter(|keyword| self.keyword_is_active(*keyword))
1093 {
1094 return Ok(TokenKind::Keyword(keyword));
1095 }
1096 let identifier = Identifier {
1097 raw,
1098 has_escape: false,
1099 keyword_hint,
1100 escaped_reserved_word: false,
1101 };
1102 return Ok(if private {
1103 TokenKind::PrivateIdentifier(identifier)
1104 } else {
1105 TokenKind::Identifier(identifier)
1106 });
1107 }
1108 }
1109 self.scan_identifier_with_value(private)
1110 .map(|(kind, _)| kind)
1111 }
1112
1113 fn scan_identifier_with_value(
1117 &mut self,
1118 private: bool,
1119 ) -> Result<(TokenKind<'a>, Option<String>), LexError> {
1120 let start = self.current_position();
1121 let raw_start = self.offset;
1122 if private {
1123 self.bump_char();
1124 }
1125 let value_start = self.offset;
1126
1127 let mut value: Option<String> = None;
1128 let mut value_utf16_len = usize::from(private);
1132 let mut has_escape = false;
1133 let mut identifier_flags = 0u8;
1134 let mut first = true;
1135
1136 loop {
1137 if let Some(&byte) = self.source.as_bytes().get(self.offset)
1140 && byte < 0x80
1141 && byte != b'\\'
1142 {
1143 let valid = if first {
1144 is_ascii_identifier_start_byte(byte)
1145 } else {
1146 is_ascii_identifier_continue_byte(byte)
1147 };
1148 if valid {
1149 identifier_flags |= ASCII_FLAGS[byte as usize];
1150 let bytes = self.source.as_bytes();
1151 let mut end = self.offset + 1;
1152 while let Some(&next) = bytes.get(end) {
1153 if next < 0x80 && is_ascii_identifier_continue_byte(next) {
1154 identifier_flags |= ASCII_FLAGS[next as usize];
1155 end += 1;
1156 } else {
1157 break;
1158 }
1159 }
1160 let run_len = end - self.offset;
1161 let Some(length) = value_utf16_len
1162 .checked_add(run_len)
1163 .filter(|length| *length <= self.string_limit)
1164 else {
1165 let consumed = self.string_limit - value_utf16_len + 1;
1168 self.offset += consumed;
1169 self.column = self.column.saturating_add(consumed as u32);
1170 return Err(self.string_too_long(start));
1171 };
1172 value_utf16_len = length;
1173 if let Some(value) = value.as_mut() {
1174 value.push_str(&self.source[self.offset..end]);
1175 }
1176 self.column = self.column.saturating_add(run_len as u32);
1177 self.offset = end;
1178 first = false;
1179 continue;
1180 }
1181 }
1182
1183 let Some(ch) = self.peek_char() else {
1184 if first {
1185 return Err(self.error_from(
1186 start,
1187 LexErrorKind::InvalidPrivateIdentifier,
1188 "invalid first character of private name",
1189 ));
1190 }
1191 break;
1192 };
1193
1194 if ch == '\\' {
1195 let checkpoint = (self.offset, self.line, self.column);
1196 let attempted_unicode_escape = self.starts_with("\\u");
1197 if !first && attempted_unicode_escape {
1198 has_escape = true;
1204 }
1205 let decoded = match self.scan_identifier_escape() {
1206 Ok(decoded) => decoded,
1207 Err(_) if first && !private => {
1208 (self.offset, self.line, self.column) = checkpoint;
1209 self.bump_char();
1210 return Ok((TokenKind::RawAscii(b'\\'), None));
1211 }
1212 Err(_) if first => {
1213 (self.offset, self.line, self.column) = checkpoint;
1214 return Err(self.error_from(
1215 start,
1216 LexErrorKind::InvalidPrivateIdentifier,
1217 "invalid first character of private name",
1218 ));
1219 }
1220 Err(_) => {
1221 (self.offset, self.line, self.column) = checkpoint;
1222 break;
1223 }
1224 };
1225 let valid = if first {
1226 is_identifier_start_code_point(decoded)
1227 } else {
1228 is_identifier_continue_code_point(decoded)
1229 };
1230 if !valid {
1231 (self.offset, self.line, self.column) = checkpoint;
1232 if !first {
1233 break;
1234 }
1235 if private {
1236 return Err(self.error_from(
1237 start,
1238 LexErrorKind::InvalidPrivateIdentifier,
1239 "invalid first character of private name",
1240 ));
1241 }
1242 self.bump_char();
1243 return Ok((TokenKind::RawAscii(b'\\'), None));
1244 }
1245 let decoded =
1246 char::from_u32(decoded).expect("ID_Start/ID_Continue excluded a surrogate");
1247 value_utf16_len = RuntimeJsString::checked_length_with_limit(
1248 value_utf16_len,
1249 decoded.len_utf16(),
1250 self.string_limit,
1251 )
1252 .map_err(|_| self.string_too_long(start))?;
1253 value
1254 .get_or_insert_with(|| self.source[value_start..checkpoint.0].to_owned())
1255 .push(decoded);
1256 has_escape = true;
1257 first = false;
1258 continue;
1259 }
1260
1261 let valid = if first {
1262 is_identifier_start(ch)
1263 } else {
1264 is_identifier_continue(ch)
1265 };
1266 if valid {
1267 identifier_flags |= NOT_KEYWORD;
1268 self.bump_char();
1269 value_utf16_len = RuntimeJsString::checked_length_with_limit(
1270 value_utf16_len,
1271 ch.len_utf16(),
1272 self.string_limit,
1273 )
1274 .map_err(|_| self.string_too_long(start))?;
1275 if let Some(value) = value.as_mut() {
1276 value.push(ch);
1277 }
1278 first = false;
1279 continue;
1280 }
1281
1282 if !first && (is_line_terminator(ch) || is_js_whitespace(ch)) {
1287 break;
1288 }
1289 if first {
1290 return Err(self.error_from(
1291 start,
1292 if private {
1293 LexErrorKind::InvalidPrivateIdentifier
1294 } else {
1295 LexErrorKind::UnexpectedCharacter
1296 },
1297 if private {
1298 "invalid first character of private name"
1299 } else {
1300 "invalid first character of identifier"
1301 },
1302 ));
1303 }
1304 break;
1305 }
1306
1307 let raw = &self.source[raw_start..self.offset];
1308 let keyword_hint = match &value {
1309 Some(value) => keyword_from_str(value),
1310 None if identifier_flags & NOT_KEYWORD == 0 => {
1311 keyword_from_str(&raw[usize::from(private)..])
1312 }
1313 None => None,
1314 };
1315 let active_keyword = keyword_hint.filter(|keyword| self.keyword_is_active(*keyword));
1316 let identifier = Identifier {
1317 raw,
1318 has_escape,
1319 keyword_hint,
1320 escaped_reserved_word: has_escape && active_keyword.is_some(),
1321 };
1322
1323 let kind = if private {
1324 TokenKind::PrivateIdentifier(identifier)
1325 } else if !has_escape {
1326 if let Some(keyword) = active_keyword {
1327 TokenKind::Keyword(keyword)
1328 } else {
1329 TokenKind::Identifier(identifier)
1330 }
1331 } else {
1332 TokenKind::Identifier(identifier)
1333 };
1334 Ok((kind, value))
1335 }
1336
1337 pub fn decode_identifier_text(&self, raw: &str) -> String {
1341 let private = raw.starts_with('#');
1342 let start = raw.as_ptr() as usize - self.source.as_ptr() as usize;
1343 let mut lexer = self.clone();
1344 lexer.offset = start;
1349 match lexer.scan_identifier_with_value(private) {
1350 Ok((_, Some(value))) => value,
1351 _ => raw[usize::from(private)..].to_owned(),
1352 }
1353 }
1354
1355 pub fn decode_string_literal(&self, start: Position) -> Result<JsString, LexError> {
1359 let mut lexer = self.clone();
1360 lexer.seek(start);
1361 let mut value = Utf16Sink::new(lexer.string_limit);
1362 lexer.scan_string_with_sink(&mut value)?;
1363 Ok(value.into_js_string())
1364 }
1365
1366 pub fn decode_template_raw_value(
1370 &self,
1371 start: Position,
1372 initial: bool,
1373 ) -> Result<JsString, LexError> {
1374 let mut lexer = self.clone();
1375 lexer.seek(start);
1376 if !initial {
1377 lexer.bump_char();
1378 }
1379 let mut raw_value = Utf16Sink::new(lexer.string_limit);
1380 let mut no_cooked: Option<ValidateSink> = None;
1381 lexer.scan_template_with_sinks(
1382 initial,
1383 false,
1384 &mut raw_value,
1385 &mut no_cooked,
1386 &mut None,
1387 )?;
1388 Ok(raw_value.into_js_string())
1389 }
1390
1391 pub fn decode_template_cooked_value(
1394 &self,
1395 start: Position,
1396 initial: bool,
1397 ) -> Result<JsString, LexError> {
1398 let mut lexer = self.clone();
1399 lexer.seek(start);
1400 if !initial {
1401 lexer.bump_char();
1402 }
1403 let mut raw_value = ValidateSink::new(lexer.string_limit);
1404 let mut cooked = Some(Utf16Sink::new(lexer.string_limit));
1405 let token = lexer.scan_template_with_sinks(
1406 initial,
1407 false,
1408 &mut raw_value,
1409 &mut cooked,
1410 &mut None,
1411 )?;
1412 let TokenKind::Template(part) = token.kind else {
1413 unreachable!("template scan must return a template token");
1414 };
1415 debug_assert!(!part.invalid_escape);
1416 Ok(cooked.expect("cooked sink present").into_js_string())
1417 }
1418
1419 pub fn template_escape_error(
1423 &self,
1424 start: Position,
1425 initial: bool,
1426 ) -> Result<Option<TemplateEscapeError>, LexError> {
1427 let mut lexer = self.clone();
1428 lexer.seek(start);
1429 if !initial {
1430 lexer.bump_char();
1431 }
1432 let mut raw = ValidateSink::new(lexer.string_limit);
1433 let mut cooked = Some(ValidateSink::new(lexer.string_limit));
1434 let mut invalid = None;
1435 lexer.scan_template_with_sinks(initial, false, &mut raw, &mut cooked, &mut invalid)?;
1436 Ok(invalid)
1437 }
1438
1439 fn scan_identifier_escape(&mut self) -> Result<u32, LexError> {
1440 let start = self.current_position();
1441 self.bump_char();
1442 if self.invalid_source_byte_at(self.offset) {
1443 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
1444 }
1445 if self.peek_char() != Some('u') {
1446 return Err(self.error_from(
1447 start,
1448 LexErrorKind::InvalidEscape,
1449 "identifier escapes must use a Unicode escape",
1450 ));
1451 }
1452 self.bump_char();
1453 self.scan_unicode_escape_value(start)
1454 }
1455
1456 fn keyword_is_active(&self, keyword: Keyword) -> bool {
1457 use Keyword::*;
1458 match keyword {
1459 Implements | Interface | Let | Package | Private | Protected | Public | Static => {
1460 self.options.context.strict
1461 }
1462 Yield => self.options.context.strict || self.options.context.generator,
1463 Await => self.options.context.module || self.options.context.async_function,
1464 _ => true,
1465 }
1466 }
1467
1468 fn scan_number(&mut self, initial_dot: bool) -> Result<TokenKind<'a>, LexError> {
1469 let start = self.current_position();
1470 let raw_start = self.offset;
1471
1472 if !initial_dot && self.peek_char() == Some('0') {
1473 let prefix = self.peek_nth_char(1);
1474 let radix = match prefix {
1475 Some('b' | 'B') => Some(NumericRadix::Binary),
1476 Some('o' | 'O') => Some(NumericRadix::Octal),
1477 Some('x' | 'X') => Some(NumericRadix::Hexadecimal),
1478 _ => None,
1479 };
1480 if let Some(radix) = radix {
1481 self.bump_char();
1482 self.bump_char();
1483 let base = radix_value(radix);
1484 let run = self.scan_digit_run(base)?;
1485 if run.digits == 0 {
1486 return Err(self.error_from(
1487 start,
1488 LexErrorKind::InvalidNumber,
1489 "invalid number literal",
1490 ));
1491 }
1492
1493 let kind = if self.peek_char() == Some('n') {
1494 self.bump_char();
1495 NumberKind::BigInt(radix)
1496 } else {
1497 NumberKind::Integer(radix)
1498 };
1499 self.reject_number_follow(start)?;
1500 return Ok(TokenKind::Number(NumberLiteral {
1501 raw: &self.source[raw_start..self.offset],
1502 kind,
1503 }));
1504 }
1505 }
1506
1507 let mut is_float = initial_dot;
1508 let mut had_separator = false;
1509 let mut integer_digits = 0;
1510 let mut leading_zero = false;
1511 let mut saw_8_or_9 = false;
1512 let mut legacy_octal_integer = false;
1513
1514 if initial_dot {
1515 self.bump_char();
1516 let run = self.scan_digit_run(10)?;
1517 if run.digits == 0 {
1518 return Err(self.error_from(
1519 start,
1520 LexErrorKind::InvalidNumber,
1521 "fraction requires a decimal digit",
1522 ));
1523 }
1524 had_separator |= run.had_separator;
1525 } else {
1526 leading_zero = self.peek_char() == Some('0');
1527 if leading_zero
1530 && self.options.context.strict
1531 && self
1532 .peek_nth_char(1)
1533 .is_some_and(|next| next.is_ascii_digit())
1534 {
1535 return Err(self.error_from(
1536 start,
1537 LexErrorKind::InvalidNumber,
1538 "octal literals are deprecated in strict mode",
1539 ));
1540 }
1541 if leading_zero && self.peek_nth_char(1) == Some('_') {
1542 self.bump_char();
1543 self.bump_char();
1544 return Err(self.error_from(
1545 start,
1546 LexErrorKind::InvalidNumber,
1547 "numeric separator cannot follow a leading zero",
1548 ));
1549 }
1550 let run = self.scan_digit_run(10)?;
1551 integer_digits = run.digits;
1552 had_separator |= run.had_separator;
1553 saw_8_or_9 |= run.saw_8_or_9;
1554 legacy_octal_integer = leading_zero && integer_digits > 1 && !saw_8_or_9;
1555 if leading_zero && integer_digits > 1 && had_separator {
1556 return Err(self.error_from(
1557 start,
1558 LexErrorKind::InvalidNumber,
1559 "numeric separators are not allowed in legacy leading-zero literals",
1560 ));
1561 }
1562
1563 if self.peek_char() == Some('.') && !legacy_octal_integer {
1564 is_float = true;
1565 self.bump_char();
1566 let run = self.scan_digit_run(10)?;
1567 had_separator |= run.had_separator;
1568 }
1569 }
1570
1571 if matches!(self.peek_char(), Some('e' | 'E')) && !legacy_octal_integer {
1572 is_float = true;
1573 self.bump_char();
1574 if matches!(self.peek_char(), Some('+' | '-')) {
1575 self.bump_char();
1576 }
1577 let run = self.scan_digit_run(10)?;
1578 if run.digits == 0 {
1579 return Err(self.error_from(
1580 start,
1581 LexErrorKind::InvalidNumber,
1582 "exponent requires at least one decimal digit",
1583 ));
1584 }
1585 had_separator |= run.had_separator;
1586 }
1587
1588 let legacy_leading_zero = leading_zero && integer_digits > 1;
1589 if legacy_leading_zero && had_separator {
1590 return Err(self.error_from(
1591 start,
1592 LexErrorKind::InvalidNumber,
1593 "numeric separators are not allowed in legacy leading-zero literals",
1594 ));
1595 }
1596
1597 let kind = if self.peek_char() == Some('n') {
1598 if is_float {
1599 self.bump_char();
1600 return Err(self.error_from(
1601 start,
1602 LexErrorKind::InvalidNumber,
1603 "invalid number literal",
1604 ));
1605 }
1606 if legacy_leading_zero {
1607 self.bump_char();
1608 return Err(self.error_from(
1609 start,
1610 LexErrorKind::InvalidNumber,
1611 "invalid number literal",
1612 ));
1613 }
1614 self.bump_char();
1615 NumberKind::BigInt(NumericRadix::Decimal)
1616 } else if is_float {
1617 NumberKind::Float
1618 } else if legacy_leading_zero && !had_separator {
1619 if saw_8_or_9 {
1620 NumberKind::LegacyDecimal
1621 } else {
1622 NumberKind::LegacyOctal
1623 }
1624 } else {
1625 NumberKind::Integer(NumericRadix::Decimal)
1626 };
1627
1628 self.reject_number_follow(start)?;
1629 Ok(TokenKind::Number(NumberLiteral {
1630 raw: &self.source[raw_start..self.offset],
1631 kind,
1632 }))
1633 }
1634
1635 fn scan_digit_run(&mut self, base: u32) -> Result<DigitRun, LexError> {
1636 let mut result = DigitRun::default();
1637 let mut previous_was_digit = false;
1638
1639 while let Some(ch) = self.peek_char() {
1640 if ch.is_digit(base) {
1641 self.bump_char();
1642 result.digits += 1;
1643 result.saw_8_or_9 |= matches!(ch, '8' | '9');
1644 previous_was_digit = true;
1645 continue;
1646 }
1647
1648 if ch == '_' {
1649 let separator = self.current_position();
1650 let next_is_digit = self
1651 .peek_nth_char(1)
1652 .is_some_and(|next| next.is_digit(base));
1653 if !previous_was_digit || !next_is_digit {
1654 self.bump_char();
1655 return Err(self.error_from(
1656 separator,
1657 LexErrorKind::InvalidNumber,
1658 "numeric separator must occur between two digits",
1659 ));
1660 }
1661 self.bump_char();
1662 result.had_separator = true;
1663 previous_was_digit = false;
1664 continue;
1665 }
1666 break;
1667 }
1668 Ok(result)
1669 }
1670
1671 fn reject_number_follow(&mut self, start: Position) -> Result<(), LexError> {
1672 let Some(ch) = self.peek_char() else {
1673 return Ok(());
1674 };
1675 if is_line_terminator(ch) || is_js_whitespace(ch) {
1676 return Ok(());
1677 }
1678 if is_identifier_continue(ch) {
1679 self.bump_char();
1680 return Err(self.error_from(
1681 start,
1682 LexErrorKind::InvalidNumber,
1683 "invalid number literal",
1684 ));
1685 }
1686 Ok(())
1687 }
1688
1689 fn scan_string(&mut self) -> Result<TokenKind<'a>, LexError> {
1690 let mut value = ValidateSink::new(self.string_limit);
1691 let literal = self.scan_string_with_sink(&mut value)?;
1692 Ok(TokenKind::String(literal))
1693 }
1694
1695 fn scan_string_with_sink(
1696 &mut self,
1697 value: &mut impl StringSink,
1698 ) -> Result<StringLiteral<'a>, LexError> {
1699 let start = self.current_position();
1700 let raw_start = self.offset;
1701 let separator = self.bump_char().expect("called at quote");
1702 let mut has_escape = false;
1703 let mut has_legacy_octal_escape = false;
1704
1705 loop {
1706 let bytes = self.source.as_bytes();
1710 let mut end = self.offset;
1711 while let Some(&byte) = bytes.get(end) {
1712 if byte >= 0x7f || byte == separator as u8 || matches!(byte, b'\\' | b'\r' | b'\n')
1713 {
1714 break;
1715 }
1716 end += 1;
1717 }
1718 if end != self.offset {
1719 let result = value.push_ascii(&bytes[self.offset..end]);
1720 let consumed = match result {
1721 Ok(()) => end - self.offset,
1722 Err(consumed) => consumed,
1723 };
1724 self.offset += consumed;
1725 self.column = self.column.saturating_add(consumed as u32);
1726 if result.is_err() {
1727 return Err(self.string_too_long(start));
1728 }
1729 }
1730 if self.invalid_source_byte_at(self.offset) {
1731 return Err(self.error_from(
1732 start,
1733 LexErrorKind::UnexpectedCharacter,
1734 "invalid UTF-8 sequence",
1735 ));
1736 }
1737 let Some(ch) = self.peek_char() else {
1738 return Err(self.error_from(
1739 start,
1740 LexErrorKind::UnterminatedString,
1741 "unexpected end of string",
1742 ));
1743 };
1744 if ch == separator {
1745 self.bump_char();
1746 return Ok(StringLiteral {
1747 raw: &self.source[raw_start..self.offset],
1748 has_escape,
1749 has_legacy_octal_escape,
1750 });
1751 }
1752 if matches!(ch, '\r' | '\n') {
1753 return Err(self.error_from(
1758 start,
1759 LexErrorKind::UnterminatedString,
1760 "unexpected end of string",
1761 ));
1762 }
1763 if ch == '\\' {
1764 has_escape = true;
1765 let escape = match self.scan_escape_sequence(false) {
1766 Err(error)
1767 if error.kind == LexErrorKind::UnexpectedCharacter
1768 && error.message == "invalid UTF-8 sequence" =>
1769 {
1770 return Err(self.error_from(
1771 start,
1772 LexErrorKind::UnexpectedCharacter,
1773 error.message,
1774 ));
1775 }
1776 Err(error)
1780 if error.kind == LexErrorKind::UnterminatedString
1781 && error.message == "unexpected end of string" =>
1782 {
1783 return Err(self.error_from(
1784 start,
1785 LexErrorKind::UnterminatedString,
1786 error.message,
1787 ));
1788 }
1789 result => result?,
1790 };
1791 has_legacy_octal_escape |= escape.legacy_octal;
1792 if let Some(code_point) = escape.code_point {
1793 value
1794 .push_code_point(code_point)
1795 .map_err(|_| self.string_too_long(start))?;
1796 }
1797 continue;
1798 }
1799
1800 let lone_surrogate = self.surrogate_at(self.offset);
1801 self.bump_char();
1802 if let Some(unit) = lone_surrogate {
1803 value
1804 .push_code_unit(unit)
1805 .map_err(|_| self.string_too_long(start))?;
1806 } else {
1807 value
1808 .push_char(ch)
1809 .map_err(|_| self.string_too_long(start))?;
1810 }
1811 }
1812 }
1813
1814 fn scan_escape_sequence(&mut self, template: bool) -> Result<EscapeValue, LexError> {
1815 let start = self.current_position();
1816 debug_assert_eq!(self.peek_char(), Some('\\'));
1817 self.bump_char();
1818 if self.invalid_source_byte_at(self.offset) {
1819 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
1820 }
1821 let Some(ch) = self.peek_char() else {
1822 return Err(self.error_from(
1827 start,
1828 LexErrorKind::UnterminatedString,
1829 "unexpected end of string",
1830 ));
1831 };
1832
1833 if is_line_terminator(ch) {
1834 self.bump_char();
1835 return Ok(EscapeValue::line_continuation());
1836 }
1837
1838 let simple = match ch {
1839 '\'' => Some('\'' as u32),
1840 '"' => Some('"' as u32),
1841 '\\' => Some('\\' as u32),
1842 'b' => Some(0x08),
1843 'f' => Some(0x0c),
1844 'n' => Some('\n' as u32),
1845 'r' => Some('\r' as u32),
1846 't' => Some('\t' as u32),
1847 'v' => Some(0x0b),
1848 _ => None,
1849 };
1850 if let Some(code_point) = simple {
1851 self.bump_char();
1852 return Ok(EscapeValue::code_point(code_point));
1853 }
1854
1855 if ch == '0'
1856 && !self
1857 .peek_nth_char(1)
1858 .is_some_and(|next| next.is_ascii_digit())
1859 {
1860 self.bump_char();
1861 return Ok(EscapeValue::code_point(0));
1862 }
1863
1864 match ch {
1865 'x' => {
1866 self.bump_char();
1867 let value = self
1868 .scan_fixed_hex_digits(2, start)
1869 .map_err(|error| self.remap_string_escape_utf8(error, start, template))?;
1870 Ok(EscapeValue::code_point(value))
1871 }
1872 'u' => {
1873 self.bump_char();
1874 let value = self
1875 .scan_unicode_escape_value(start)
1876 .map_err(|error| self.remap_string_escape_utf8(error, start, template))?;
1877 Ok(EscapeValue::code_point(value))
1878 }
1879 '0'..='7' => {
1880 if template || self.options.context.strict {
1881 self.bump_char();
1882 return Err(self.error_from(
1883 start,
1884 LexErrorKind::InvalidEscape,
1885 if self.options.context.strict {
1886 "octal escape sequences are not allowed in strict mode"
1887 } else {
1888 "legacy octal escape is not allowed in this context"
1889 },
1890 ));
1891 }
1892 let value = self.scan_legacy_octal_escape();
1893 Ok(EscapeValue::legacy(value))
1894 }
1895 '8' | '9' => {
1896 if template || self.options.context.strict {
1897 self.bump_char();
1898 return Err(self.error_from(
1902 start,
1903 LexErrorKind::InvalidEscape,
1904 "malformed escape sequence in string literal",
1905 ));
1906 }
1907 self.bump_char();
1908 Ok(EscapeValue::legacy(ch as u32))
1909 }
1910 _ => {
1911 let lone_surrogate = self.surrogate_at(self.offset);
1912 self.bump_char();
1913 Ok(EscapeValue::code_point(
1914 lone_surrogate.map_or(ch as u32, u32::from),
1915 ))
1916 }
1917 }
1918 }
1919
1920 fn scan_legacy_octal_escape(&mut self) -> u32 {
1921 let first = self.peek_char().expect("called at octal digit");
1922 let max_digits = if matches!(first, '0'..='3') { 3 } else { 2 };
1923 let mut value = 0_u32;
1924 let mut count = 0;
1925 while count < max_digits {
1926 let Some(ch @ '0'..='7') = self.peek_char() else {
1927 break;
1928 };
1929 self.bump_char();
1930 value = (value * 8) + ch.to_digit(8).expect("matched octal digit");
1931 count += 1;
1932 }
1933 value
1934 }
1935
1936 fn remap_string_escape_utf8(
1937 &self,
1938 error: LexError,
1939 start: Position,
1940 template: bool,
1941 ) -> LexError {
1942 if !template
1943 && error.kind == LexErrorKind::UnexpectedCharacter
1944 && error.message == "invalid UTF-8 sequence"
1945 {
1946 self.error_from(
1947 start,
1948 LexErrorKind::InvalidEscape,
1949 "malformed escape sequence in string literal",
1950 )
1951 } else {
1952 error
1953 }
1954 }
1955
1956 fn scan_unicode_escape_value(&mut self, start: Position) -> Result<u32, LexError> {
1957 if self.peek_char() == Some('{') {
1958 self.bump_char();
1959 let mut digits = 0;
1960 let mut value = 0_u32;
1961 while let Some(ch) = self.peek_char() {
1962 if self.invalid_source_byte_at(self.offset) {
1963 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
1964 }
1965 let Some(digit) = ch.to_digit(16) else {
1966 break;
1967 };
1968 self.bump_char();
1969 value = value
1970 .checked_mul(16)
1971 .and_then(|value| value.checked_add(digit))
1972 .filter(|value| *value <= 0x10ffff)
1973 .unwrap_or(0x110000);
1974 digits += 1;
1975 }
1976 if digits == 0 || self.peek_char() != Some('}') {
1977 return Err(self.error_from(
1978 start,
1979 LexErrorKind::InvalidEscape,
1980 "malformed escape sequence in string literal",
1981 ));
1982 }
1983 self.bump_char();
1984 if value > 0x10ffff {
1985 return Err(self.error_from(
1986 start,
1987 LexErrorKind::InvalidEscape,
1988 "malformed escape sequence in string literal",
1989 ));
1990 }
1991 Ok(value)
1992 } else {
1993 self.scan_fixed_hex_digits(4, start)
1994 }
1995 }
1996
1997 fn scan_fixed_hex_digits(&mut self, count: usize, start: Position) -> Result<u32, LexError> {
1998 let mut value = 0_u32;
1999 for _ in 0..count {
2000 if self.invalid_source_byte_at(self.offset) {
2001 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
2002 }
2003 let Some(ch) = self.peek_char() else {
2007 return Err(self.error_from(
2008 start,
2009 LexErrorKind::InvalidEscape,
2010 "malformed escape sequence in string literal",
2011 ));
2012 };
2013 let Some(digit) = ch.to_digit(16) else {
2014 self.bump_char();
2015 return Err(self.error_from(
2016 start,
2017 LexErrorKind::InvalidEscape,
2018 "malformed escape sequence in string literal",
2019 ));
2020 };
2021 self.bump_char();
2022 value = (value << 4) | digit;
2023 }
2024 Ok(value)
2025 }
2026
2027 fn scan_template(
2028 &mut self,
2029 initial: bool,
2030 line_terminator_before: bool,
2031 ) -> Result<Token<'a>, LexError> {
2032 let mut raw_value = ValidateSink::new(self.string_limit);
2033 let mut cooked = Some(ValidateSink::new(self.string_limit));
2034 self.scan_template_with_sinks(
2035 initial,
2036 line_terminator_before,
2037 &mut raw_value,
2038 &mut cooked,
2039 &mut None,
2040 )
2041 }
2042
2043 fn scan_template_with_sinks(
2044 &mut self,
2045 initial: bool,
2046 line_terminator_before: bool,
2047 raw_value: &mut impl StringSink,
2048 cooked: &mut Option<impl StringSink>,
2049 invalid_escape: &mut Option<TemplateEscapeError>,
2050 ) -> Result<Token<'a>, LexError> {
2051 let start = if initial {
2052 self.current_position()
2053 } else {
2054 let current = self.current_position();
2055 if current.byte_offset == 0
2056 || self.source.as_bytes().get(current.byte_offset - 1) != Some(&b'}')
2057 {
2058 return Err(self.error_from(
2059 current,
2060 LexErrorKind::ExpectedTemplateContinuation,
2061 "template continuation must immediately follow '}'",
2062 ));
2063 }
2064 Position::new(
2065 current.byte_offset - 1,
2066 current.line,
2067 current.column.saturating_sub(1),
2068 )
2069 };
2070 if initial {
2071 debug_assert_eq!(self.peek_char(), Some(TEMPLATE_QUOTE));
2072 self.bump_char();
2073 }
2074 let raw_start = self.offset;
2075
2076 loop {
2077 if self.invalid_source_byte_at(self.offset) {
2078 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
2079 }
2080 let Some(ch) = self.peek_char() else {
2081 return Err(self.error_from(
2082 start,
2083 LexErrorKind::UnterminatedTemplate,
2084 "unexpected end of string",
2085 ));
2086 };
2087
2088 if ch == TEMPLATE_QUOTE {
2089 let raw_end = self.offset;
2090 self.bump_char();
2091 let kind = if initial {
2092 TemplatePartKind::NoSubstitution
2093 } else {
2094 TemplatePartKind::Tail
2095 };
2096 return Ok(Token {
2097 kind: TokenKind::Template(TemplatePart {
2098 raw: &self.source[raw_start..raw_end],
2099 invalid_escape: invalid_escape.is_some(),
2100 kind,
2101 }),
2102 span: Span::new(start, self.current_position()),
2103 line_terminator_before,
2104 });
2105 }
2106
2107 if ch == '$' && self.peek_nth_char(1) == Some('{') {
2108 let raw_end = self.offset;
2109 self.bump_char();
2110 self.bump_char();
2111 let kind = if initial {
2112 TemplatePartKind::Head
2113 } else {
2114 TemplatePartKind::Middle
2115 };
2116 return Ok(Token {
2117 kind: TokenKind::Template(TemplatePart {
2118 raw: &self.source[raw_start..raw_end],
2119 invalid_escape: invalid_escape.is_some(),
2120 kind,
2121 }),
2122 span: Span::new(start, self.current_position()),
2123 line_terminator_before,
2124 });
2125 }
2126
2127 if ch == '\\' {
2128 let escape_start = self.offset;
2129 let mut cooked_cursor = self.clone();
2135 let escape = cooked_cursor.scan_escape_sequence(true);
2136 if let Err(error) = &escape
2137 && error.kind == LexErrorKind::UnexpectedCharacter
2138 && error.message == "invalid UTF-8 sequence"
2139 {
2140 return Err(*error);
2141 }
2142 if escape.is_ok() {
2143 *self = cooked_cursor;
2144 } else {
2145 self.bump_char();
2146 self.bump_char();
2147 }
2148 self.append_template_raw_source(raw_value, escape_start..self.offset)
2149 .map_err(|_| self.string_too_long(start))?;
2150 match escape {
2151 Ok(escape) => {
2152 if let (Some(value), Some(output)) = (escape.code_point, cooked.as_mut()) {
2153 output
2154 .push_code_point(value)
2155 .map_err(|_| self.string_too_long(start))?;
2156 }
2157 }
2158 Err(error) => {
2159 if invalid_escape.is_none() {
2160 *invalid_escape = Some(TemplateEscapeError {
2161 message: error.message,
2162 span: error.span,
2163 });
2164 }
2165 *cooked = None;
2166 }
2167 }
2168 continue;
2169 }
2170
2171 let lone_surrogate = self.surrogate_at(self.offset);
2172 self.bump_char();
2173 if let Some(unit) = lone_surrogate {
2174 raw_value
2175 .push_code_unit(unit)
2176 .map_err(|_| self.string_too_long(start))?;
2177 if let Some(output) = cooked.as_mut() {
2178 output
2179 .push_code_unit(unit)
2180 .map_err(|_| self.string_too_long(start))?;
2181 }
2182 } else {
2183 raw_value
2184 .push_char(if ch == '\r' { '\n' } else { ch })
2185 .map_err(|_| self.string_too_long(start))?;
2186 if let Some(output) = cooked.as_mut() {
2187 if ch == '\r' {
2188 output
2189 .push_char('\n')
2190 .map_err(|_| self.string_too_long(start))?;
2191 } else {
2192 output
2193 .push_char(ch)
2194 .map_err(|_| self.string_too_long(start))?;
2195 }
2196 }
2197 }
2198 }
2199 }
2200
2201 fn append_template_raw_source(
2202 &self,
2203 value: &mut impl StringSink,
2204 range: Range<usize>,
2205 ) -> Result<(), JsStringError> {
2206 let units = self
2207 .semantic_utf16_units(range)
2208 .expect("template raw range is a trusted source slice");
2209 append_template_raw_units(&mut *value, units)
2210 }
2211
2212 fn scan_regexp(
2213 &mut self,
2214 start: Position,
2215 line_terminator_before: bool,
2216 ) -> Result<Token<'a>, LexError> {
2217 debug_assert_eq!(self.peek_char(), Some('/'));
2218 self.bump_char();
2219 let pattern_start = self.offset;
2220 let mut in_character_class = false;
2221
2222 let pattern_end = loop {
2223 if self.invalid_source_byte_at(self.offset) {
2224 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
2225 }
2226 let Some(ch) = self.peek_char() else {
2227 return Err(self.error_from(
2228 start,
2229 LexErrorKind::UnterminatedRegExp,
2230 "unexpected end of regexp",
2231 ));
2232 };
2233 if is_line_terminator(ch) {
2234 return Err(self.error_here(
2235 LexErrorKind::LineTerminatorInRegExp,
2236 "unexpected line terminator in regexp",
2237 ));
2238 }
2239 if ch == '/' && !in_character_class {
2240 let end = self.offset;
2241 self.bump_char();
2242 break end;
2243 }
2244 if ch == '\\' {
2245 self.bump_char();
2246 if self.invalid_source_byte_at(self.offset) {
2247 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
2248 }
2249 let Some(escaped) = self.peek_char() else {
2250 return Err(self.error_from(
2251 start,
2252 LexErrorKind::UnterminatedRegExp,
2253 "unexpected end of regexp",
2254 ));
2255 };
2256 if is_line_terminator(escaped) {
2257 return Err(self.error_here(
2258 LexErrorKind::LineTerminatorInRegExp,
2259 "unexpected line terminator in regexp",
2260 ));
2261 }
2262 self.bump_char();
2263 continue;
2264 }
2265 if ch == '[' {
2266 in_character_class = true;
2267 } else if ch == ']' {
2268 in_character_class = false;
2269 }
2270 self.bump_char();
2271 };
2272
2273 let flags_start = self.offset;
2274 while let Some(ch) = self.peek_char() {
2275 if self.invalid_source_byte_at(self.offset) {
2276 return Err(self.invalid_utf8_error_here("invalid UTF-8 sequence"));
2277 }
2278 if is_identifier_continue(ch) {
2279 self.bump_char();
2280 } else {
2281 break;
2282 }
2283 }
2284
2285 Ok(Token {
2286 kind: TokenKind::RegExp(RegExpLiteral {
2287 pattern: &self.source[pattern_start..pattern_end],
2288 flags: &self.source[flags_start..self.offset],
2289 }),
2290 span: Span::new(start, self.current_position()),
2291 line_terminator_before,
2292 })
2293 }
2294
2295 fn scan_punctuator(&mut self) -> Result<TokenKind<'a>, LexError> {
2296 use Punctuator::*;
2297
2298 let bytes = self.source.as_bytes();
2299 let byte = bytes[self.offset];
2300 let at = |offset: usize| bytes.get(self.offset + offset).copied();
2301
2302 let (len, punctuator) = match byte {
2303 b'>' => match (at(1), at(2), at(3)) {
2304 (Some(b'>'), Some(b'>'), Some(b'=')) => (4, UnsignedShiftRightAssign),
2305 (Some(b'>'), Some(b'>'), _) => (3, UnsignedShiftRight),
2306 (Some(b'>'), Some(b'='), _) => (3, ShiftRightAssign),
2307 (Some(b'>'), _, _) => (2, ShiftRight),
2308 (Some(b'='), _, _) => (2, GreaterEqual),
2309 _ => (1, Greater),
2310 },
2311 b'=' => match (at(1), at(2)) {
2312 (Some(b'='), Some(b'=')) => (3, StrictEqual),
2313 (Some(b'='), _) => (2, EqualEqual),
2314 (Some(b'>'), _) => (2, Arrow),
2315 _ => (1, Equal),
2316 },
2317 b'!' => match (at(1), at(2)) {
2318 (Some(b'='), Some(b'=')) => (3, StrictNotEqual),
2319 (Some(b'='), _) => (2, NotEqual),
2320 _ => (1, Not),
2321 },
2322 b'*' => match (at(1), at(2)) {
2323 (Some(b'*'), Some(b'=')) => (3, ExponentAssign),
2324 (Some(b'*'), _) => (2, Exponent),
2325 (Some(b'='), _) => (2, MultiplyAssign),
2326 _ => (1, Multiply),
2327 },
2328 b'<' => match (at(1), at(2)) {
2329 (Some(b'<'), Some(b'=')) => (3, ShiftLeftAssign),
2330 (Some(b'<'), _) => (2, ShiftLeft),
2331 (Some(b'='), _) => (2, LessEqual),
2332 _ => (1, Less),
2333 },
2334 b'&' => match (at(1), at(2)) {
2335 (Some(b'&'), Some(b'=')) => (3, LogicalAndAssign),
2336 (Some(b'&'), _) => (2, LogicalAnd),
2337 (Some(b'='), _) => (2, BitAndAssign),
2338 _ => (1, BitAnd),
2339 },
2340 b'|' => match (at(1), at(2)) {
2341 (Some(b'|'), Some(b'=')) => (3, LogicalOrAssign),
2342 (Some(b'|'), _) => (2, LogicalOr),
2343 (Some(b'='), _) => (2, BitOrAssign),
2344 _ => (1, BitOr),
2345 },
2346 b'?' => {
2347 if at(1) == Some(b'.') && !at(2).is_some_and(|byte| byte.is_ascii_digit()) {
2348 (2, OptionalChain)
2349 } else {
2350 match (at(1), at(2)) {
2351 (Some(b'?'), Some(b'=')) => (3, NullishAssign),
2352 (Some(b'?'), _) => (2, NullishCoalesce),
2353 _ => (1, Question),
2354 }
2355 }
2356 }
2357 b'.' => match (at(1), at(2)) {
2358 (Some(b'.'), Some(b'.')) => (3, Ellipsis),
2359 _ => (1, Dot),
2360 },
2361 b'-' => match at(1) {
2362 Some(b'-') => (2, Decrement),
2363 Some(b'=') => (2, MinusAssign),
2364 _ => (1, Minus),
2365 },
2366 b'+' => match at(1) {
2367 Some(b'+') => (2, Increment),
2368 Some(b'=') => (2, PlusAssign),
2369 _ => (1, Plus),
2370 },
2371 b'/' => match at(1) {
2372 Some(b'=') => (2, DivideAssign),
2373 _ => (1, Divide),
2374 },
2375 b'%' => match at(1) {
2376 Some(b'=') => (2, RemainderAssign),
2377 _ => (1, Remainder),
2378 },
2379 b'^' => match at(1) {
2380 Some(b'=') => (2, BitXorAssign),
2381 _ => (1, BitXor),
2382 },
2383 b'{' => (1, LeftBrace),
2384 b'}' => (1, RightBrace),
2385 b'(' => (1, LeftParen),
2386 b')' => (1, RightParen),
2387 b'[' => (1, LeftBracket),
2388 b']' => (1, RightBracket),
2389 b';' => (1, Semicolon),
2390 b',' => (1, Comma),
2391 b'~' => (1, BitNot),
2392 b':' => (1, Colon),
2393 _ => {
2394 let ch = self.peek_char().expect("called before end of source");
2395 if ch.is_ascii() {
2396 self.bump_char();
2397 return Ok(TokenKind::RawAscii(ch as u8));
2398 }
2399 return Err(
2400 self.error_here(LexErrorKind::UnexpectedCharacter, "unexpected character")
2401 );
2402 }
2403 };
2404
2405 self.offset += len;
2406 self.column = self.column.saturating_add(len as u32);
2407 Ok(TokenKind::Punctuator(punctuator))
2408 }
2409}
2410
2411impl<'a> Iterator for Lexer<'a> {
2412 type Item = Result<Token<'a>, LexError>;
2413
2414 fn next(&mut self) -> Option<Self::Item> {
2415 if self.eof_emitted || self.failed {
2416 return None;
2417 }
2418 let result = self.next_token();
2419 if result.is_err() {
2420 self.failed = true;
2421 }
2422 Some(result)
2423 }
2424}
2425
2426impl FusedIterator for Lexer<'_> {}
2427
2428#[derive(Clone, Copy, Debug, Default)]
2429struct DigitRun {
2430 digits: usize,
2431 had_separator: bool,
2432 saw_8_or_9: bool,
2433}
2434
2435#[derive(Clone, Copy, Debug)]
2436struct EscapeValue {
2437 code_point: Option<u32>,
2438 legacy_octal: bool,
2439}
2440
2441impl EscapeValue {
2442 const fn code_point(value: u32) -> Self {
2443 Self {
2444 code_point: Some(value),
2445 legacy_octal: false,
2446 }
2447 }
2448
2449 const fn legacy(value: u32) -> Self {
2450 Self {
2451 code_point: Some(value),
2452 legacy_octal: true,
2453 }
2454 }
2455
2456 const fn line_continuation() -> Self {
2457 Self {
2458 code_point: None,
2459 legacy_octal: false,
2460 }
2461 }
2462}
2463
2464fn is_line_terminator(ch: char) -> bool {
2465 matches!(ch, '\n' | '\r' | '\u{2028}' | '\u{2029}')
2466}
2467
2468pub fn quickjs_simple_lookahead_has_line_terminator(mut source: &str) -> bool {
2474 loop {
2475 let Some(ch) = source.chars().next() else {
2476 return false;
2477 };
2478 match ch {
2479 '\r' | '\n' | '\u{2028}' | '\u{2029}' => return true,
2480 '/' if source.starts_with("//") => {
2481 return true;
2484 }
2485 '/' if source.starts_with("/*") => {
2486 let Some(end) = source[2..].find("*/") else {
2487 return false;
2488 };
2489 let body = &source[2..2 + end];
2490 if body.bytes().any(|byte| matches!(byte, b'\r' | b'\n')) {
2491 return true;
2492 }
2493 source = &source[2 + end + 2..];
2494 }
2495 ch if is_js_whitespace(ch) => {
2496 source = &source[ch.len_utf8()..];
2497 }
2498 _ => return false,
2499 }
2500 }
2501}
2502
2503pub fn quickjs_simple_lookahead_is_of(mut source: &str) -> bool {
2508 loop {
2509 let Some(ch) = source.chars().next() else {
2510 return false;
2511 };
2512 if source.starts_with("//") {
2513 let Some(end) = source
2514 .as_bytes()
2515 .iter()
2516 .position(|byte| matches!(byte, b'\r' | b'\n'))
2517 else {
2518 return false;
2519 };
2520 source = &source[end..];
2521 } else if source.starts_with("/*") {
2522 let Some(end) = source[2..].find("*/") else {
2523 return false;
2524 };
2525 source = &source[2 + end + 2..];
2526 } else if is_line_terminator(ch) || is_js_whitespace(ch) {
2527 source = &source[ch.len_utf8()..];
2528 } else {
2529 break;
2530 }
2531 }
2532
2533 let Some(after) = source.strip_prefix("of") else {
2534 return false;
2535 };
2536 after
2537 .chars()
2538 .next()
2539 .is_none_or(|ch| !is_identifier_continue(ch))
2540}
2541
2542#[must_use]
2548#[cfg(test)]
2549pub fn quickjs_detect_module(source: &str) -> bool {
2550 quickjs_detect_module_bytes(source.as_bytes())
2551}
2552
2553#[must_use]
2561pub fn quickjs_detect_module_bytes(mut source: &[u8]) -> bool {
2562 source = quickjs_detect_module_skip_shebang(source);
2563 let Some((token, rest)) = quickjs_detect_module_token(source) else {
2564 return false;
2565 };
2566 match token {
2567 QuickJsDetectModuleToken::Export => true,
2568 QuickJsDetectModuleToken::Import => !matches!(
2569 quickjs_detect_module_token(rest).map(|(token, _)| token),
2570 Some(QuickJsDetectModuleToken::Dot | QuickJsDetectModuleToken::LeftParen)
2571 ),
2572 QuickJsDetectModuleToken::Dot
2573 | QuickJsDetectModuleToken::LeftParen
2574 | QuickJsDetectModuleToken::Other => false,
2575 }
2576}
2577
2578#[derive(Clone, Copy)]
2579enum QuickJsDetectModuleToken {
2580 Import,
2581 Export,
2582 Dot,
2583 LeftParen,
2584 Other,
2585}
2586
2587fn quickjs_detect_module_skip_shebang(source: &[u8]) -> &[u8] {
2588 if !source.starts_with(b"#!") {
2589 return source;
2590 }
2591
2592 let mut offset = 2;
2593 while offset < source.len() {
2594 match source[offset] {
2595 b'\r' | b'\n' => break,
2596 byte if byte >= 0x80 => match decode_quickjs_utf8(&source[offset..]) {
2597 Some((0x2028 | 0x2029, consumed)) => {
2598 offset += consumed;
2599 break;
2600 }
2601 Some((_, consumed)) => offset += consumed,
2602 None => offset += 1,
2603 },
2604 _ => offset += 1,
2605 }
2606 }
2607 &source[offset..]
2608}
2609
2610fn quickjs_detect_module_token(mut source: &[u8]) -> Option<(QuickJsDetectModuleToken, &[u8])> {
2611 loop {
2612 let &byte = source.first()?;
2613 if source.starts_with(b"//") {
2614 let (offset, &terminator) = source
2615 .iter()
2616 .enumerate()
2617 .find(|(_, byte)| matches!(byte, b'\0' | b'\r' | b'\n'))?;
2618 if terminator == b'\0' {
2619 return None;
2620 }
2621 source = &source[offset..];
2622 } else if source.starts_with(b"/*") {
2623 let body = &source[2..];
2624 let mut offset = 0;
2625 loop {
2626 let &body_byte = body.get(offset)?;
2627 if body_byte == b'\0' {
2628 return None;
2629 }
2630 if body_byte == b'*' && body.get(offset + 1) == Some(&b'/') {
2631 source = &body[offset + 2..];
2632 break;
2633 }
2634 offset += 1;
2635 }
2636 } else {
2637 let (code_point, consumed) = if byte < 0x80 {
2638 (u32::from(byte), 1)
2639 } else {
2640 let Some(decoded) = decode_quickjs_utf8(source) else {
2641 break;
2642 };
2643 decoded
2644 };
2645 let is_space = char::from_u32(code_point)
2646 .is_some_and(|ch| is_line_terminator(ch) || is_js_whitespace(ch));
2647 if !is_space {
2648 break;
2649 }
2650 source = &source[consumed..];
2651 }
2652 }
2653
2654 if let Some(rest) = source.strip_prefix(b"import")
2655 && !quickjs_detect_module_starts_identifier_continue(rest)
2656 {
2657 return Some((QuickJsDetectModuleToken::Import, rest));
2658 } else if let Some(rest) = source.strip_prefix(b"export")
2659 && !quickjs_detect_module_starts_identifier_continue(rest)
2660 {
2661 return Some((QuickJsDetectModuleToken::Export, rest));
2662 } else if let Some(rest) = source.strip_prefix(b".") {
2663 return Some((QuickJsDetectModuleToken::Dot, rest));
2664 } else if let Some(rest) = source.strip_prefix(b"(") {
2665 return Some((QuickJsDetectModuleToken::LeftParen, rest));
2666 }
2667 Some((QuickJsDetectModuleToken::Other, source))
2668}
2669
2670fn quickjs_detect_module_starts_identifier_continue(source: &[u8]) -> bool {
2671 decode_quickjs_utf8(source)
2672 .is_some_and(|(code_point, _)| is_identifier_continue_code_point(code_point))
2673}
2674
2675fn is_js_whitespace(ch: char) -> bool {
2676 matches!(
2677 ch,
2678 '\t' | '\u{000b}' | '\u{000c}' | ' ' | '\u{00a0}' | '\u{1680}' | '\u{2000}'
2679 ..='\u{200a}' | '\u{202f}' | '\u{205f}' | '\u{3000}' | '\u{feff}'
2680 )
2681}
2682
2683fn is_ascii_identifier_start(ch: char) -> bool {
2684 ch.is_ascii_alphabetic() || matches!(ch, '_' | '$')
2685}
2686
2687fn is_ascii_identifier_continue(ch: char) -> bool {
2688 is_ascii_identifier_start(ch) || ch.is_ascii_digit()
2689}
2690
2691const IDENTIFIER_START: u8 = 1;
2694const IDENTIFIER_CONTINUE: u8 = 2;
2695const NOT_KEYWORD: u8 = 4;
2696const MAY_START_TRIVIA: u8 = 8;
2697const ASCII_FLAGS: [u8; 128] = {
2698 let mut table = [0; 128];
2699 let mut byte = 0u8;
2700 while byte < 128 {
2701 let start = byte.is_ascii_alphabetic() || matches!(byte, b'_' | b'$');
2702 table[byte as usize] = if start {
2703 IDENTIFIER_START | IDENTIFIER_CONTINUE
2704 } else if byte.is_ascii_digit() {
2705 IDENTIFIER_CONTINUE
2706 } else {
2707 0
2708 };
2709 if !byte.is_ascii_lowercase() {
2710 table[byte as usize] |= NOT_KEYWORD;
2711 }
2712 if matches!(
2713 byte,
2714 b' ' | b'\t' | b'\r' | b'\n' | 0x0b | 0x0c | b'/' | b'<' | b'-' | b'#'
2715 ) {
2716 table[byte as usize] |= MAY_START_TRIVIA;
2717 }
2718 byte += 1;
2719 }
2720 table
2721};
2722
2723#[derive(Clone, Copy)]
2724enum ScanKind {
2725 Single(Punctuator),
2726 Identifier,
2727 Number,
2728 String,
2729 Template,
2730 Dot,
2731 Private,
2732 Punctuator,
2733 Slow,
2734}
2735
2736const ASCII_DISPATCH: [ScanKind; 128] = {
2737 let mut table = [ScanKind::Punctuator; 128];
2738 let mut byte = 0u8;
2739 while byte < 128 {
2740 if ASCII_FLAGS[byte as usize] & IDENTIFIER_START != 0 {
2741 table[byte as usize] = ScanKind::Identifier;
2742 }
2743 if byte.is_ascii_digit() {
2744 table[byte as usize] = ScanKind::Number;
2745 }
2746 byte += 1;
2747 }
2748 table[b'\\' as usize] = ScanKind::Slow;
2749 table[b'\'' as usize] = ScanKind::String;
2750 table[b'"' as usize] = ScanKind::String;
2751 table[b'`' as usize] = ScanKind::Template;
2752 table[b'.' as usize] = ScanKind::Dot;
2753 table[b'#' as usize] = ScanKind::Private;
2754 table[b'{' as usize] = ScanKind::Single(Punctuator::LeftBrace);
2755 table[b'}' as usize] = ScanKind::Single(Punctuator::RightBrace);
2756 table[b'(' as usize] = ScanKind::Single(Punctuator::LeftParen);
2757 table[b')' as usize] = ScanKind::Single(Punctuator::RightParen);
2758 table[b'[' as usize] = ScanKind::Single(Punctuator::LeftBracket);
2759 table[b']' as usize] = ScanKind::Single(Punctuator::RightBracket);
2760 table[b';' as usize] = ScanKind::Single(Punctuator::Semicolon);
2761 table[b',' as usize] = ScanKind::Single(Punctuator::Comma);
2762 table[b':' as usize] = ScanKind::Single(Punctuator::Colon);
2763 table[b'~' as usize] = ScanKind::Single(Punctuator::BitNot);
2764 table
2765};
2766
2767fn is_ascii_identifier_start_byte(byte: u8) -> bool {
2768 ASCII_FLAGS[byte as usize] & IDENTIFIER_START != 0
2769}
2770
2771fn is_ascii_identifier_continue_byte(byte: u8) -> bool {
2772 ASCII_FLAGS[byte as usize] & IDENTIFIER_CONTINUE != 0
2773}
2774
2775fn is_identifier_start(ch: char) -> bool {
2776 is_identifier_start_code_point(ch as u32)
2777}
2778
2779fn is_identifier_continue(ch: char) -> bool {
2780 is_identifier_continue_code_point(ch as u32)
2781}
2782
2783fn is_identifier_start_code_point(code_point: u32) -> bool {
2784 if code_point < 0x80 {
2785 char::from_u32(code_point).is_some_and(is_ascii_identifier_start)
2786 } else {
2787 crate::source::unicode::is_id_start(code_point)
2788 }
2789}
2790
2791fn is_identifier_continue_code_point(code_point: u32) -> bool {
2792 if code_point < 0x80 {
2793 char::from_u32(code_point).is_some_and(is_ascii_identifier_continue)
2794 } else {
2795 matches!(code_point, 0x200c | 0x200d) || crate::source::unicode::is_id_continue(code_point)
2796 }
2797}
2798
2799fn radix_value(radix: NumericRadix) -> u32 {
2800 match radix {
2801 NumericRadix::Binary => 2,
2802 NumericRadix::Octal => 8,
2803 NumericRadix::Decimal => 10,
2804 NumericRadix::Hexadecimal => 16,
2805 }
2806}
2807
2808fn append_template_raw_units(
2809 value: &mut impl StringSink,
2810 units: impl IntoIterator<Item = u16>,
2811) -> Result<(), JsStringError> {
2812 let mut units = units.into_iter().peekable();
2813 while let Some(unit) = units.next() {
2814 if unit == u16::from(b'\r') {
2815 if units.peek() == Some(&u16::from(b'\n')) {
2816 units.next();
2817 }
2818 value.push_code_unit(u16::from(b'\n'))?;
2819 } else {
2820 value.push_code_unit(unit)?;
2821 }
2822 }
2823 Ok(())
2824}
2825
2826fn keyword_from_str(value: &str) -> Option<Keyword> {
2827 use Keyword::*;
2828 Some(match value {
2829 "null" => Null,
2830 "false" => False,
2831 "true" => True,
2832 "if" => If,
2833 "else" => Else,
2834 "return" => Return,
2835 "var" => Var,
2836 "this" => This,
2837 "delete" => Delete,
2838 "void" => Void,
2839 "typeof" => Typeof,
2840 "new" => New,
2841 "in" => In,
2842 "instanceof" => Instanceof,
2843 "do" => Do,
2844 "while" => While,
2845 "for" => For,
2846 "break" => Break,
2847 "continue" => Continue,
2848 "switch" => Switch,
2849 "case" => Case,
2850 "default" => Default,
2851 "throw" => Throw,
2852 "try" => Try,
2853 "catch" => Catch,
2854 "finally" => Finally,
2855 "function" => Function,
2856 "debugger" => Debugger,
2857 "with" => With,
2858 "class" => Class,
2859 "const" => Const,
2860 "enum" => Enum,
2861 "export" => Export,
2862 "extends" => Extends,
2863 "import" => Import,
2864 "super" => Super,
2865 "implements" => Implements,
2866 "interface" => Interface,
2867 "let" => Let,
2868 "package" => Package,
2869 "private" => Private,
2870 "protected" => Protected,
2871 "public" => Public,
2872 "static" => Static,
2873 "yield" => Yield,
2874 "await" => Await,
2875 _ => return None,
2876 })
2877}
2878
2879#[cfg(test)]
2880mod tests {
2881 use super::*;
2882
2883 fn kinds(source: &str) -> Vec<TokenKind<'_>> {
2884 Lexer::new(source)
2885 .tokenize()
2886 .expect("source should tokenize")
2887 .into_iter()
2888 .map(|token| token.kind)
2889 .collect()
2890 }
2891
2892 fn number_kinds(source: &str) -> Vec<(&str, NumberKind)> {
2893 Lexer::new(source)
2894 .tokenize()
2895 .expect("numbers should tokenize")
2896 .into_iter()
2897 .filter_map(|token| match token.kind {
2898 TokenKind::Number(number) => Some((number.raw, number.kind)),
2899 _ => None,
2900 })
2901 .collect()
2902 }
2903
2904 #[test]
2905 fn tracks_utf8_spans_lines_and_asi_trivia() {
2906 let source = "\"é\";\r\n/* one\n two */ const x = 1";
2907 let tokens = Lexer::new(source).tokenize().unwrap();
2908
2909 assert_eq!(tokens[0].span.start, Position::new(0, 1, 1));
2910 assert_eq!(tokens[0].span.end, Position::new(4, 1, 4));
2911 assert!(matches!(tokens[2].kind, TokenKind::Keyword(Keyword::Const)));
2912 assert!(tokens[2].line_terminator_before);
2913 assert_eq!(tokens[2].span.start.line, 3);
2914 assert_eq!(tokens[2].span.start.column, 9);
2915 assert!(!tokens[3].line_terminator_before);
2916 }
2917
2918 #[test]
2919 fn classifies_keywords_with_quickjs_context_rules() {
2920 let tokens = Lexer::new("const let yield await").tokenize().unwrap();
2921 assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Const)));
2922 for token in &tokens[1..4] {
2923 assert!(matches!(
2924 token.kind,
2925 TokenKind::Identifier(Identifier {
2926 keyword_hint: Some(_),
2927 ..
2928 })
2929 ));
2930 }
2931
2932 let options = LexerOptions {
2933 context: LexContext {
2934 strict: true,
2935 module: true,
2936 ..LexContext::default()
2937 },
2938 ..LexerOptions::default()
2939 };
2940 let tokens = Lexer::with_options("let yield await", options)
2941 .tokenize()
2942 .unwrap();
2943 assert!(matches!(tokens[0].kind, TokenKind::Keyword(Keyword::Let)));
2944 assert!(matches!(tokens[1].kind, TokenKind::Keyword(Keyword::Yield)));
2945 assert!(matches!(tokens[2].kind, TokenKind::Keyword(Keyword::Await)));
2946 }
2947
2948 #[test]
2949 fn escaped_keyword_remains_identifier_with_reserved_marker() {
2950 let source = r"\u0069f";
2951 let token = Lexer::new(source).next_token().unwrap();
2952 match token.kind {
2953 TokenKind::Identifier(identifier) => {
2954 assert_eq!(
2955 Lexer::new(source).decode_identifier_text(identifier.raw),
2956 "if"
2957 );
2958 assert!(identifier.has_escape);
2959 assert_eq!(identifier.keyword_hint, Some(Keyword::If));
2960 assert!(identifier.escaped_reserved_word);
2961 }
2962 other => panic!("expected identifier, got {other:?}"),
2963 }
2964
2965 for source in [r"if\u{}", r"if\u{2d}"] {
2966 let token = Lexer::new(source).next_token().unwrap();
2967 let TokenKind::Identifier(identifier) = token.kind else {
2968 panic!("expected attempted Unicode escape to retain identifier form");
2969 };
2970 assert_eq!(identifier.raw, "if");
2971 assert!(identifier.has_escape);
2972 assert!(identifier.escaped_reserved_word);
2973 }
2974 assert!(matches!(
2975 Lexer::new(r"if\x61").next_token().unwrap().kind,
2976 TokenKind::Keyword(Keyword::If)
2977 ));
2978 }
2979
2980 #[test]
2981 fn escaped_identifier_decode_and_rewind_after_a_long_minified_line() {
2982 let source = format!("{}\\u0061\\u{{62}}é;\r\n\\u{{10400}};", "x;".repeat(4096));
2983 let mut lexer = Lexer::new(&source);
2984 let mut escaped = Vec::new();
2985 loop {
2986 let token = lexer.next_token().unwrap();
2987 if let TokenKind::Identifier(identifier) = token.kind
2988 && identifier.has_escape
2989 {
2990 escaped.push((token, identifier));
2991 }
2992 if matches!(token.kind, TokenKind::Eof) {
2993 break;
2994 }
2995 }
2996 assert_eq!(escaped[0].0.span.start, Position::new(8192, 1, 8193));
2997 assert_eq!(escaped[1].0.span.start.line, 2);
2998 assert_eq!(escaped[1].0.span.start.column, 1);
2999 for ((token, identifier), expected) in escaped.into_iter().zip(["abé", "𐐀"]) {
3000 assert_eq!(lexer.decode_identifier_text(identifier.raw), expected);
3001 lexer.seek(token.span.start);
3002 let replayed = lexer.next_token().unwrap();
3003 assert_eq!(replayed.kind, token.kind);
3004 assert_eq!(replayed.span, token.span);
3005 }
3006 }
3007
3008 #[test]
3009 fn ascii_identifier_path_matches_full_scanner_at_context_and_length_boundaries() {
3010 for context in [
3011 LexContext::default(),
3012 LexContext {
3013 strict: true,
3014 ..LexContext::default()
3015 },
3016 LexContext {
3017 generator: true,
3018 async_function: true,
3019 module: true,
3020 ..LexContext::default()
3021 },
3022 ] {
3023 for source in [
3024 "name;",
3025 "a",
3026 "async()",
3027 "yield ",
3028 "await",
3029 "let",
3030 "Name9_",
3031 "alphaé;",
3032 "éclair;",
3033 r"if\u{2d}",
3034 r"f\u006fo",
3035 "alpha\u{a0}",
3036 "alpha\u{2028}",
3037 "#name",
3038 "#yield",
3039 "#é",
3040 r"#\u0061",
3041 "#9",
3042 ] {
3043 for limit in [1, 2, 5, 31, RuntimeJsString::MAX_LEN] {
3044 let mut fast = Lexer::with_options(
3045 source,
3046 LexerOptions {
3047 context,
3048 ..LexerOptions::default()
3049 },
3050 )
3051 .with_string_limit(limit);
3052 let mut full = fast.clone();
3053 let private = source.starts_with('#');
3054 let expected = full
3055 .scan_identifier_with_value(private)
3056 .map(|(kind, _)| kind);
3057 assert_eq!(
3058 fast.scan_identifier(private),
3059 expected,
3060 "{source:?}, {context:?}, limit {limit}"
3061 );
3062 assert_eq!(fast.current_position(), full.current_position());
3063 }
3064 }
3065 }
3066 }
3067
3068 #[test]
3069 fn unicode_identifiers_use_pinned_start_continue_and_escape_rules() {
3070 let source = "π变量𐐀a\u{0300}\u{200c}\u{200d}";
3071 let token = Lexer::new(source).next_token().unwrap();
3072 let TokenKind::Identifier(identifier) = token.kind else {
3073 panic!("expected a Unicode identifier");
3074 };
3075 assert_eq!(identifier.raw, source);
3076 assert_eq!(
3077 Lexer::new(source).decode_identifier_text(identifier.raw),
3078 source
3079 );
3080 assert!(!identifier.has_escape);
3081 assert_eq!(
3082 token.span,
3083 Span::new(Position::new(0, 1, 1), Position::new(21, 1, 9))
3084 );
3085
3086 let escaped_source = r"\u03c0\u{10400}a\u0300";
3087 let escaped = Lexer::new(escaped_source).next_token().unwrap();
3088 let TokenKind::Identifier(identifier) = escaped.kind else {
3089 panic!("expected an escaped Unicode identifier");
3090 };
3091 assert_eq!(
3092 Lexer::new(escaped_source).decode_identifier_text(identifier.raw),
3093 "π𐐀a\u{0300}"
3094 );
3095 assert!(identifier.has_escape);
3096
3097 let leading_zero_source = r"\u{0000000000010400}";
3098 let leading_zero_escape = Lexer::new(leading_zero_source).next_token().unwrap();
3099 let TokenKind::Identifier(identifier) = leading_zero_escape.kind else {
3100 panic!("expected a leading-zero Unicode escape identifier");
3101 };
3102 assert_eq!(
3103 Lexer::new(leading_zero_source).decode_identifier_text(identifier.raw),
3104 "𐐀"
3105 );
3106
3107 let escaped_tail_source = r"a\u{2d}";
3108 let mut escaped_invalid_tail = Lexer::new(escaped_tail_source);
3109 let TokenKind::Identifier(identifier) = escaped_invalid_tail.next_token().unwrap().kind
3110 else {
3111 panic!("expected the valid identifier prefix");
3112 };
3113 assert_eq!(
3114 Lexer::new(escaped_tail_source).decode_identifier_text(identifier.raw),
3115 "a"
3116 );
3117 assert!(identifier.has_escape);
3118 assert!(matches!(
3119 escaped_invalid_tail.next_token().unwrap().kind,
3120 TokenKind::RawAscii(b'\\')
3121 ));
3122
3123 assert_eq!(
3124 Lexer::new("😀").next_token().unwrap_err().kind,
3125 LexErrorKind::UnexpectedCharacter
3126 );
3127 for source in [r"\u0300", r"\u{}", r"\u{2d}", r"\x61"] {
3128 let token = Lexer::new(source).next_token().unwrap();
3129 assert!(matches!(token.kind, TokenKind::RawAscii(b'\\')));
3130 assert_eq!(token.span.end.byte_offset, 1);
3131 }
3132 assert!(matches!(
3133 Lexer::new("@").next_token().unwrap().kind,
3134 TokenKind::RawAscii(b'@')
3135 ));
3136 let mut invalid_tail = Lexer::new("ascii😀");
3137 assert!(matches!(
3138 invalid_tail.next_token().unwrap().kind,
3139 TokenKind::Identifier(_)
3140 ));
3141 assert_eq!(
3142 invalid_tail.next_token().unwrap_err().kind,
3143 LexErrorKind::UnexpectedCharacter
3144 );
3145 }
3146
3147 #[test]
3148 fn scans_private_identifiers_and_identifier_escapes() {
3149 let private_source = r"#pr\u0069vate";
3150 let token = Lexer::new(private_source).next_token().unwrap();
3151 match token.kind {
3152 TokenKind::PrivateIdentifier(identifier) => {
3153 assert_eq!(identifier.raw, private_source);
3154 assert_eq!(
3155 Lexer::new(private_source).decode_identifier_text(identifier.raw),
3156 "private"
3157 );
3158 assert!(identifier.has_escape);
3159 }
3160 other => panic!("expected private identifier, got {other:?}"),
3161 }
3162 for source in ["#", "#1", "# "] {
3163 let error = Lexer::new(source).next_token().unwrap_err();
3164 assert_eq!(error.kind, LexErrorKind::InvalidPrivateIdentifier);
3165 assert_eq!(error.message, "invalid first character of private name");
3166 }
3167 for source in [r"#\u{}", r"#\u{2d}", r"#\u{d800}"] {
3168 let error = Lexer::new(source).next_token().unwrap_err();
3169 assert_eq!(error.kind, LexErrorKind::InvalidPrivateIdentifier);
3170 assert_eq!(error.span.start, Position::new(0, 1, 1));
3171 }
3172 let unicode_private_source = "#π\u{0300}";
3173 let token = Lexer::new(unicode_private_source).next_token().unwrap();
3174 let TokenKind::PrivateIdentifier(identifier) = token.kind else {
3175 panic!("expected a Unicode private identifier");
3176 };
3177 assert_eq!(
3178 Lexer::new(unicode_private_source).decode_identifier_text(identifier.raw),
3179 "π\u{0300}"
3180 );
3181
3182 for source in [r"#a\u{}", r"#a\u{2d}", r"#a\uD800"] {
3183 let mut lexer = Lexer::new(source);
3184 let TokenKind::PrivateIdentifier(identifier) = lexer.next_token().unwrap().kind else {
3185 panic!("expected the valid private-name prefix for {source}");
3186 };
3187 assert_eq!(
3188 Lexer::new(source).decode_identifier_text(identifier.raw),
3189 "a"
3190 );
3191 assert!(identifier.has_escape);
3192 assert!(matches!(
3193 lexer.next_token().unwrap().kind,
3194 TokenKind::RawAscii(b'\\')
3195 ));
3196 }
3197 }
3198
3199 #[test]
3200 fn scans_numeric_radices_floats_exponents_separators_and_bigints() {
3201 assert_eq!(
3202 number_kinds("0 42 1_000 0b1010 0o755 0xCA_FE .5 1. 2e-3 99n 0xffn"),
3203 vec![
3204 ("0", NumberKind::Integer(NumericRadix::Decimal)),
3205 ("42", NumberKind::Integer(NumericRadix::Decimal)),
3206 ("1_000", NumberKind::Integer(NumericRadix::Decimal)),
3207 ("0b1010", NumberKind::Integer(NumericRadix::Binary)),
3208 ("0o755", NumberKind::Integer(NumericRadix::Octal)),
3209 ("0xCA_FE", NumberKind::Integer(NumericRadix::Hexadecimal)),
3210 (".5", NumberKind::Float),
3211 ("1.", NumberKind::Float),
3212 ("2e-3", NumberKind::Float),
3213 ("99n", NumberKind::BigInt(NumericRadix::Decimal)),
3214 ("0xffn", NumberKind::BigInt(NumericRadix::Hexadecimal)),
3215 ]
3216 );
3217 assert_eq!(
3218 number_kinds("077 089"),
3219 vec![
3220 ("077", NumberKind::LegacyOctal),
3221 ("089", NumberKind::LegacyDecimal),
3222 ]
3223 );
3224 }
3225
3226 #[test]
3227 fn malformed_number_diagnostic_matches_quickjs_wording() {
3228 for source in [
3236 "0x", "0X", "0b", "0B", "0o", "0O", "0b2n", "0xgn", "0o8", "1.0n", ".5n", "0e0n",
3237 "1E2n", "01n", "00n", "012348n", "0008n",
3238 ] {
3239 let error = Lexer::new(source).next_token().unwrap_err();
3240 assert_eq!(error.kind, LexErrorKind::InvalidNumber, "{source}");
3241 assert_eq!(error.message, "invalid number literal", "{source}");
3242 assert_eq!(error.span.start, Position::new(0, 1, 1), "{source}");
3243 }
3244 }
3245
3246 #[test]
3247 fn rejects_malformed_numbers() {
3248 for source in [
3249 "0b2",
3250 "0o8",
3251 "0x",
3252 "1__0",
3253 "1_",
3254 "1e+",
3255 "1.0n",
3256 "01n",
3257 "00_1",
3258 "08.1_2",
3259 "08e1_2",
3260 "10instanceof",
3261 "1π",
3262 "1\u{0300}",
3263 ] {
3264 let error = Lexer::new(source).next_token().unwrap_err();
3265 assert_eq!(
3266 error.kind,
3267 LexErrorKind::InvalidNumber,
3268 "unexpected error for {source}"
3269 );
3270 }
3271
3272 let options = LexerOptions {
3273 context: LexContext {
3274 strict: true,
3275 ..LexContext::default()
3276 },
3277 ..LexerOptions::default()
3278 };
3279 assert_eq!(
3280 Lexer::with_options("077", options)
3281 .next_token()
3282 .unwrap_err()
3283 .kind,
3284 LexErrorKind::InvalidNumber
3285 );
3286
3287 let mut escaped_identifier = Lexer::new(r"1\u0061");
3288 assert!(matches!(
3289 escaped_identifier.next_token().unwrap().kind,
3290 TokenKind::Number(_)
3291 ));
3292 let TokenKind::Identifier(identifier) = escaped_identifier.next_token().unwrap().kind
3293 else {
3294 panic!("expected escaped identifier after the number");
3295 };
3296 assert_eq!(
3297 escaped_identifier.decode_identifier_text(identifier.raw),
3298 "a"
3299 );
3300 }
3301
3302 #[test]
3303 fn decodes_string_escapes_without_losing_lone_surrogates() {
3304 let source = r#""a\n\x42\u{43}\uD800" '\141\8\0'"#;
3305 let lexer = Lexer::new(source);
3306 let tokens = lexer.clone().tokenize().unwrap();
3307 let TokenKind::String(_) = &tokens[0].kind else {
3308 panic!("expected string");
3309 };
3310 let first = lexer.decode_string_literal(tokens[0].span.start).unwrap();
3311 assert_eq!(first.utf16, vec![0x61, 0x0a, 0x42, 0x43, 0xd800]);
3312 assert!(first.to_string().is_err());
3313
3314 let TokenKind::String(second) = &tokens[1].kind else {
3315 panic!("expected string");
3316 };
3317 let second_value = lexer.decode_string_literal(tokens[1].span.start).unwrap();
3318 assert_eq!(second_value.utf16, vec![0x61, 0x38, 0]);
3319 assert!(second.has_legacy_octal_escape);
3320 }
3321
3322 #[test]
3323 fn handles_string_line_continuations_and_strict_escape_errors() {
3324 let lexer = Lexer::new("\"a\\\r\nb\"");
3325 let token = lexer.clone().next_token().unwrap();
3326 let TokenKind::String(_) = token.kind else {
3327 panic!("expected string");
3328 };
3329 assert_eq!(
3330 lexer
3331 .decode_string_literal(token.span.start)
3332 .unwrap()
3333 .to_string()
3334 .unwrap(),
3335 "ab"
3336 );
3337 assert_eq!(token.span.end.line, 2);
3338
3339 let options = LexerOptions {
3340 context: LexContext {
3341 strict: true,
3342 ..LexContext::default()
3343 },
3344 ..LexerOptions::default()
3345 };
3346 assert_eq!(
3347 Lexer::with_options(r"'\1'", options)
3348 .next_token()
3349 .unwrap_err()
3350 .kind,
3351 LexErrorKind::InvalidEscape
3352 );
3353 let zero_lexer = Lexer::with_options(r"'\0'", options);
3354 let zero = zero_lexer.clone().next_token().unwrap();
3355 let TokenKind::String(_) = zero.kind else {
3356 panic!("expected string");
3357 };
3358 assert_eq!(
3359 zero_lexer
3360 .decode_string_literal(zero.span.start)
3361 .unwrap()
3362 .utf16,
3363 vec![0]
3364 );
3365 }
3366
3367 #[test]
3368 fn longest_matches_punctuators() {
3369 let tokens = kinds(">>>= === !== **= &&= ||= ??= ... ?. ?.3 => ++ --");
3370 let punctuators: Vec<Punctuator> = tokens
3371 .into_iter()
3372 .filter_map(|kind| match kind {
3373 TokenKind::Punctuator(punctuator) => Some(punctuator),
3374 _ => None,
3375 })
3376 .collect();
3377 assert_eq!(
3378 punctuators,
3379 vec![
3380 Punctuator::UnsignedShiftRightAssign,
3381 Punctuator::StrictEqual,
3382 Punctuator::StrictNotEqual,
3383 Punctuator::ExponentAssign,
3384 Punctuator::LogicalAndAssign,
3385 Punctuator::LogicalOrAssign,
3386 Punctuator::NullishAssign,
3387 Punctuator::Ellipsis,
3388 Punctuator::OptionalChain,
3389 Punctuator::Question,
3390 Punctuator::Arrow,
3391 Punctuator::Increment,
3392 Punctuator::Decrement,
3393 ]
3394 );
3395 }
3396
3397 #[test]
3398 fn slash_interpretation_is_an_explicit_lexical_goal() {
3399 let mut division = Lexer::new("/= value");
3400 assert!(matches!(
3401 division.next_token().unwrap().kind,
3402 TokenKind::Punctuator(Punctuator::DivideAssign)
3403 ));
3404
3405 let mut regexp = Lexer::new(r"/a[\/]b+/gim");
3406 let token = regexp.next_token_with_goal(LexicalGoal::RegExp).unwrap();
3407 match token.kind {
3408 TokenKind::RegExp(literal) => {
3409 assert_eq!(literal.pattern, r"a[\/]b+");
3410 assert_eq!(literal.flags, "gim");
3411 assert_eq!(
3412 ®exp.source()[token.span.start.byte_offset..token.span.end.byte_offset],
3413 r"/a[\/]b+/gim"
3414 );
3415 }
3416 other => panic!("expected regular expression, got {other:?}"),
3417 }
3418
3419 let mut unicode_flags = Lexer::new("/x/π\u{0300}\u{200c}");
3420 let token = unicode_flags
3421 .next_token_with_goal(LexicalGoal::RegExp)
3422 .unwrap();
3423 let TokenKind::RegExp(literal) = token.kind else {
3424 panic!("expected regular expression with Unicode flags");
3425 };
3426 assert_eq!(literal.flags, "π\u{0300}\u{200c}");
3427
3428 assert_eq!(
3429 Lexer::new("value")
3430 .next_token_with_goal(LexicalGoal::RegExp)
3431 .unwrap_err()
3432 .kind,
3433 LexErrorKind::ExpectedRegExp
3434 );
3435 }
3436
3437 #[test]
3438 fn regexp_rejects_line_terminators() {
3439 let mut lexer = Lexer::new("/a\n/");
3440 assert_eq!(
3441 lexer
3442 .next_token_with_goal(LexicalGoal::RegExp)
3443 .unwrap_err()
3444 .kind,
3445 LexErrorKind::LineTerminatorInRegExp
3446 );
3447 }
3448
3449 #[test]
3450 fn reversible_source_text_restores_string_template_and_regexp_values() {
3451 let string_source = SourceText::try_from_utf16([
3452 u16::from(b'\''),
3453 0xd800,
3454 0xe000,
3455 u16::from(b'\\'),
3456 0xdfff,
3457 u16::from(b'\''),
3458 ])
3459 .unwrap();
3460 let string_lexer = Lexer::with_source_text(&string_source, LexerOptions::default());
3461 let string = string_lexer.clone().next_token().unwrap();
3462 let TokenKind::String(_) = string.kind else {
3463 panic!("expected reversible String token");
3464 };
3465 assert_eq!(
3466 string_lexer
3467 .decode_string_literal(string.span.start)
3468 .unwrap()
3469 .utf16,
3470 [0xd800, 0xe000, 0xdfff]
3471 );
3472
3473 let template_source = SourceText::try_from_utf16([
3474 u16::from(b'`'),
3475 0xd800,
3476 u16::from(b'\\'),
3477 0xdfff,
3478 u16::from(b'`'),
3479 ])
3480 .unwrap();
3481 let template_lexer = Lexer::with_source_text(&template_source, LexerOptions::default());
3482 let template = template_lexer.clone().next_token().unwrap();
3483 let TokenKind::Template(_) = template.kind else {
3484 panic!("expected reversible Template token");
3485 };
3486 assert_eq!(
3487 template_lexer
3488 .decode_template_raw_value(template.span.start, true)
3489 .unwrap()
3490 .utf16,
3491 [0xd800, 0x5c, 0xdfff]
3492 );
3493 assert_eq!(
3494 template_lexer
3495 .decode_template_cooked_value(template.span.start, true)
3496 .unwrap()
3497 .utf16,
3498 [0xd800, 0xdfff]
3499 );
3500
3501 let regexp_source = SourceText::try_from_utf16([
3502 u16::from(b'/'),
3503 0xd800,
3504 0xe000,
3505 u16::from(b'\\'),
3506 0xdfff,
3507 u16::from(b'/'),
3508 ])
3509 .unwrap();
3510 let mut lexer = Lexer::with_source_text(®exp_source, LexerOptions::default());
3511 let regexp = lexer.next_token_with_goal(LexicalGoal::RegExp).unwrap();
3512 let TokenKind::RegExp(regexp) = regexp.kind else {
3513 panic!("expected reversible RegExp token");
3514 };
3515 let pattern = lexer
3516 .source_range_to_js_string(1..1 + regexp.pattern.len())
3517 .unwrap()
3518 .unwrap();
3519 assert_eq!(
3520 pattern.utf16_units().collect::<Vec<_>>(),
3521 [0xd800, 0xe000, 0x5c, 0xdfff]
3522 );
3523 }
3524
3525 #[test]
3526 fn raw_source_invalid_bytes_are_skipped_only_inside_trivia() {
3527 let source = SourceText::try_from_raw_bytes(
3528 b"#! invalid \x80\n// invalid \xc0\x80\n/* invalid \xffX */ 42",
3529 )
3530 .unwrap();
3531 let tokens = Lexer::with_source_text(&source, LexerOptions::default())
3532 .tokenize()
3533 .unwrap();
3534
3535 let TokenKind::Number(number) = &tokens[0].kind else {
3536 panic!("expected number after raw-byte comments");
3537 };
3538 assert_eq!(number.raw, "42");
3539 assert!(matches!(tokens[1].kind, TokenKind::Eof));
3540 }
3541
3542 #[test]
3543 fn raw_source_string_diagnostics_keep_quickjs_token_context() {
3544 for (raw, kind, message, expected_offset) in [
3545 (
3546 b"'\x80'".as_slice(),
3547 LexErrorKind::UnexpectedCharacter,
3548 "invalid UTF-8 sequence",
3549 0,
3550 ),
3551 (
3552 b"'\\\x80'",
3553 LexErrorKind::UnexpectedCharacter,
3554 "invalid UTF-8 sequence",
3555 0,
3556 ),
3557 (
3558 b"'\\x\x80'",
3559 LexErrorKind::InvalidEscape,
3560 "malformed escape sequence in string literal",
3561 1,
3562 ),
3563 (
3564 b"'\\u\x80'",
3565 LexErrorKind::InvalidEscape,
3566 "malformed escape sequence in string literal",
3567 1,
3568 ),
3569 (
3570 b"'\\u{\x80}'",
3571 LexErrorKind::InvalidEscape,
3572 "malformed escape sequence in string literal",
3573 1,
3574 ),
3575 ] {
3576 let source = SourceText::try_from_raw_bytes(raw).unwrap();
3577 let error = Lexer::with_source_text(&source, LexerOptions::default())
3578 .next_token()
3579 .unwrap_err();
3580 assert_eq!(error.kind, kind, "{raw:02x?}");
3581 assert_eq!(error.message, message, "{raw:02x?}");
3582 assert_eq!(error.span.start.byte_offset, expected_offset, "{raw:02x?}");
3583 }
3584 }
3585
3586 #[test]
3587 fn newline_terminated_strings_anchor_diagnostic_at_opening_quote() {
3588 for source in ["'abc\n", "'abc\r\n", "'abc\r", "\"abc\n", "'\\\n", "'"] {
3589 let error = Lexer::new(source).next_token().unwrap_err();
3590 assert_eq!(error.kind, LexErrorKind::UnterminatedString, "{source:?}");
3591 assert_eq!(error.message, "unexpected end of string", "{source:?}");
3592 assert_eq!(
3595 (error.span.start.line, error.span.start.column),
3596 (1, 1),
3597 "{source:?}"
3598 );
3599 }
3600
3601 let second_line = Lexer::new("var t = 1;\n'abc\n").tokenize().unwrap_err();
3602 assert_eq!(second_line.kind, LexErrorKind::UnterminatedString);
3603 assert_eq!(
3604 (second_line.span.start.line, second_line.span.start.column),
3605 (2, 1)
3606 );
3607 }
3608
3609 #[test]
3610 fn malformed_string_escapes_share_pinned_quickjs_wording_and_span() {
3611 for source in [
3612 r"'\u00'",
3613 r"'\uz'",
3614 r"'\xZZ'",
3615 r"'\x'",
3616 r"'\u'",
3617 r"'\u{}'",
3618 r"'\u{zz}'",
3619 r"'\u{41'",
3620 r"'\u{110000}'",
3621 ] {
3622 let error = Lexer::new(source).next_token().unwrap_err();
3623 assert_eq!(error.kind, LexErrorKind::InvalidEscape, "{source}");
3624 assert_eq!(
3625 error.message, "malformed escape sequence in string literal",
3626 "{source}"
3627 );
3628 assert_eq!(error.span.start.byte_offset, 1, "{source}");
3629 assert_eq!(error.span.start.column, 2, "{source}");
3630 }
3631
3632 let strict = LexerOptions {
3633 context: LexContext {
3634 strict: true,
3635 ..LexContext::default()
3636 },
3637 ..LexerOptions::default()
3638 };
3639 for source in [r"'\8'", r"'\9'"] {
3640 let error = Lexer::with_options(source, strict)
3641 .next_token()
3642 .unwrap_err();
3643 assert_eq!(error.kind, LexErrorKind::InvalidEscape, "{source}");
3644 assert_eq!(
3645 error.message, "malformed escape sequence in string literal",
3646 "{source}"
3647 );
3648 }
3649 }
3650
3651 #[test]
3652 fn raw_source_template_and_regexp_diagnostics_point_at_malformed_byte() {
3653 for (raw, expected_offset) in [
3654 (b"`\x80`".as_slice(), 1),
3655 (b"`\\\x80`", 2),
3656 (b"`\\x\x80`", 3),
3657 (b"`\\u\x80`", 3),
3658 (b"`\\u{\x80}`", 4),
3659 ] {
3660 let source = SourceText::try_from_raw_bytes(raw).unwrap();
3661 let error = Lexer::with_source_text(&source, LexerOptions::default())
3662 .next_token()
3663 .unwrap_err();
3664 assert_eq!(error.kind, LexErrorKind::UnexpectedCharacter, "{raw:02x?}");
3665 assert_eq!(error.message, "invalid UTF-8 sequence", "{raw:02x?}");
3666 assert_eq!(error.span.start.byte_offset, expected_offset, "{raw:02x?}");
3667 assert!(source.invalid_byte_at(expected_offset));
3668 }
3669
3670 for raw in [b"/\x80/".as_slice(), b"/a\\\x80/", b"/a/\x80"] {
3671 let source = SourceText::try_from_raw_bytes(raw).unwrap();
3672 let error = Lexer::with_source_text(&source, LexerOptions::default())
3673 .next_token_with_goal(LexicalGoal::RegExp)
3674 .unwrap_err();
3675 assert_eq!(error.kind, LexErrorKind::UnexpectedCharacter, "{raw:02x?}");
3676 assert_eq!(error.message, "invalid UTF-8 sequence", "{raw:02x?}");
3677 assert!(source.invalid_byte_at(error.span.start.byte_offset));
3678 }
3679 }
3680
3681 #[test]
3682 fn raw_source_identifier_escape_stops_before_the_backslash() {
3683 let identifier = SourceText::try_from_raw_bytes(b"a\\u\x80").unwrap();
3684 let mut lexer = Lexer::with_source_text(&identifier, LexerOptions::default());
3685 let token = lexer.next_token().unwrap();
3686 let TokenKind::Identifier(name) = token.kind else {
3687 panic!("expected identifier before malformed escape");
3688 };
3689 assert_eq!(lexer.decode_identifier_text(name.raw), "a");
3690 assert_eq!(token.span.end.byte_offset, 1);
3691 let backslash = lexer.next_token().unwrap();
3692 assert!(matches!(backslash.kind, TokenKind::RawAscii(b'\\')));
3693 assert_eq!(backslash.span.start.byte_offset, 1);
3694 }
3695
3696 #[test]
3697 fn raw_source_malformed_bytes_never_enter_number_values() {
3698 let number = SourceText::try_from_raw_bytes(b"1\x80").unwrap();
3699 let mut lexer = Lexer::with_source_text(&number, LexerOptions::default());
3700 assert!(matches!(
3701 lexer.next_token().unwrap().kind,
3702 TokenKind::Number(_)
3703 ));
3704 let error = lexer.next_token().unwrap_err();
3705 assert_eq!(error.span.start.byte_offset, 1);
3706 assert_eq!(error.message, "unexpected character");
3707 }
3708
3709 #[test]
3710 fn genuine_del_and_pua_are_not_raw_source_markers() {
3711 let source = SourceText::try_from_raw_bytes("'\u{7f}\u{e000}'".as_bytes()).unwrap();
3712 let lexer = Lexer::with_source_text(&source, LexerOptions::default());
3713 let token = lexer.clone().next_token().unwrap();
3714 let TokenKind::String(_) = token.kind else {
3715 panic!("expected string containing genuine DEL and PUA");
3716 };
3717 assert_eq!(
3718 lexer.decode_string_literal(token.span.start).unwrap().utf16,
3719 [0x7f, 0xe000]
3720 );
3721 }
3722
3723 #[test]
3724 fn template_continuation_is_parser_driven() {
3725 let source = format!(
3726 "{}head{}{{name}}tail{}",
3727 TEMPLATE_QUOTE, '$', TEMPLATE_QUOTE
3728 );
3729 let mut lexer = Lexer::new(&source);
3730 let head_token = lexer.next_token().unwrap();
3731 let TokenKind::Template(head) = head_token.kind else {
3732 panic!("expected template head");
3733 };
3734 assert_eq!(head.kind, TemplatePartKind::Head);
3735 assert_eq!(head.raw, "head");
3736 assert_eq!(
3737 lexer
3738 .decode_template_cooked_value(head_token.span.start, true)
3739 .unwrap()
3740 .to_string()
3741 .unwrap(),
3742 "head"
3743 );
3744
3745 assert!(matches!(
3746 lexer.next_token().unwrap().kind,
3747 TokenKind::Identifier(_)
3748 ));
3749 assert!(matches!(
3750 lexer.next_token().unwrap().kind,
3751 TokenKind::Punctuator(Punctuator::RightBrace)
3752 ));
3753
3754 let tail_token = lexer
3755 .next_token_with_goal(LexicalGoal::TemplateContinuation)
3756 .unwrap();
3757 let TokenKind::Template(tail) = tail_token.kind else {
3758 panic!("expected template tail");
3759 };
3760 assert_eq!(tail.kind, TemplatePartKind::Tail);
3761 assert_eq!(tail.raw, "tail");
3762 assert_eq!(
3763 lexer
3764 .decode_template_cooked_value(tail_token.span.start, false)
3765 .unwrap()
3766 .to_string()
3767 .unwrap(),
3768 "tail"
3769 );
3770
3771 let error = Lexer::new("tail")
3772 .next_token_with_goal(LexicalGoal::TemplateContinuation)
3773 .unwrap_err();
3774 assert_eq!(error.kind, LexErrorKind::ExpectedTemplateContinuation);
3775 }
3776
3777 #[test]
3778 fn template_invalid_escape_is_preserved_for_tagged_semantics() {
3779 let source = format!("{TEMPLATE_QUOTE}bad\\8{TEMPLATE_QUOTE}");
3780 let lexer = Lexer::new(&source);
3781 let token = lexer.clone().next_token().unwrap();
3782 let TokenKind::Template(part) = token.kind else {
3783 panic!("expected template");
3784 };
3785 assert_eq!(part.kind, TemplatePartKind::NoSubstitution);
3786 assert!(part.invalid_escape);
3787 let invalid = lexer
3788 .template_escape_error(token.span.start, true)
3789 .unwrap()
3790 .expect("invalid escape metadata");
3791 assert_eq!(
3792 invalid.message,
3793 "malformed escape sequence in string literal"
3794 );
3795 }
3796
3797 #[test]
3798 fn cold_template_diagnostics_keep_multiline_positions() {
3799 let source = format!("`{}\r\né\\u{{x}}`", "a".repeat(8192));
3800 let lexer = Lexer::new(&source);
3801 let token = lexer.clone().next_token().unwrap();
3802 let TokenKind::Template(part) = token.kind else {
3803 panic!("expected template")
3804 };
3805 assert!(part.invalid_escape);
3806 let error = lexer
3807 .template_escape_error(token.span.start, true)
3808 .unwrap()
3809 .unwrap();
3810 assert_eq!(error.span.start, Position::new(8197, 2, 2));
3811 assert_eq!(error.span.end, Position::new(8200, 2, 5));
3812 }
3813
3814 #[test]
3815 fn cold_template_errors_do_not_widen_ordinary_tokens() {
3816 assert!(std::mem::size_of::<Token<'_>>() <= 80);
3817 }
3818
3819 #[test]
3820 fn malformed_template_escape_cannot_consume_a_structural_delimiter() {
3821 let token = Lexer::new("`\\x`").next_token().unwrap();
3822 let TokenKind::Template(part) = token.kind else {
3823 panic!("expected template");
3824 };
3825 assert_eq!(part.kind, TemplatePartKind::NoSubstitution);
3826 assert_eq!(part.raw, "\\x");
3827 assert!(part.invalid_escape);
3828 assert_eq!(
3829 Lexer::new("`\\x`")
3830 .template_escape_error(token.span.start, true)
3831 .unwrap()
3832 .unwrap()
3833 .span
3834 .start
3835 .column,
3836 2
3837 );
3838
3839 let mut lexer = Lexer::new("`\\x${value}`");
3840 let head_token = lexer.next_token().unwrap();
3841 let TokenKind::Template(head) = head_token.kind else {
3842 panic!("expected template head");
3843 };
3844 assert_eq!(head.kind, TemplatePartKind::Head);
3845 assert_eq!(head.raw, "\\x");
3846 assert_eq!(
3847 lexer
3848 .decode_template_raw_value(head_token.span.start, true)
3849 .unwrap()
3850 .to_string()
3851 .unwrap(),
3852 "\\x"
3853 );
3854 assert!(head.invalid_escape);
3855
3856 assert!(matches!(
3857 lexer.next_token().unwrap().kind,
3858 TokenKind::Identifier(_)
3859 ));
3860 assert!(matches!(
3861 lexer.next_token().unwrap().kind,
3862 TokenKind::Punctuator(Punctuator::RightBrace)
3863 ));
3864 let tail = lexer
3865 .next_token_with_goal(LexicalGoal::TemplateContinuation)
3866 .unwrap();
3867 let TokenKind::Template(tail) = tail.kind else {
3868 panic!("expected template tail");
3869 };
3870 assert_eq!(tail.kind, TemplatePartKind::Tail);
3871 assert_eq!(tail.raw, "");
3872
3873 let mut lexer = Lexer::new("`ok${value}tail\\x`");
3874 assert!(matches!(
3875 lexer.next_token().unwrap().kind,
3876 TokenKind::Template(TemplatePart {
3877 kind: TemplatePartKind::Head,
3878 ..
3879 })
3880 ));
3881 lexer.next_token().unwrap();
3882 lexer.next_token().unwrap();
3883 let tail_token = lexer
3884 .next_token_with_goal(LexicalGoal::TemplateContinuation)
3885 .unwrap();
3886 let TokenKind::Template(tail) = tail_token.kind else {
3887 panic!("expected malformed template tail");
3888 };
3889 assert_eq!(tail.kind, TemplatePartKind::Tail);
3890 assert_eq!(tail.raw, "tail\\x");
3891 assert_eq!(
3892 lexer
3893 .decode_template_raw_value(tail_token.span.start, false)
3894 .unwrap()
3895 .to_string()
3896 .unwrap(),
3897 "tail\\x"
3898 );
3899 assert!(tail.invalid_escape);
3900 }
3901
3902 #[test]
3903 fn template_raw_value_normalizes_only_physical_crlf() {
3904 let source = format!("{TEMPLATE_QUOTE}a\r\n\\nb{TEMPLATE_QUOTE}");
3905 let lexer = Lexer::new(&source);
3906 let token = lexer.clone().next_token().unwrap();
3907 let TokenKind::Template(part) = token.kind else {
3908 panic!("expected template");
3909 };
3910 assert_eq!(part.raw, "a\r\n\\nb");
3911 assert_eq!(
3912 lexer
3913 .decode_template_raw_value(token.span.start, true)
3914 .unwrap()
3915 .to_string()
3916 .unwrap(),
3917 "a\n\\nb"
3918 );
3919 assert_eq!(
3920 lexer
3921 .decode_template_cooked_value(token.span.start, true)
3922 .unwrap()
3923 .to_string()
3924 .unwrap(),
3925 "a\n\nb"
3926 );
3927 }
3928
3929 #[test]
3930 fn string_limit_counts_utf16_and_preserves_lexer_error_order() {
3931 let lexer = Lexer::new("'abc'").with_string_limit(3);
3932 let token = lexer.clone().next_token().unwrap();
3933 let TokenKind::String(_) = token.kind else {
3934 panic!("expected String token");
3935 };
3936 assert_eq!(
3937 lexer.decode_string_literal(token.span.start).unwrap().utf16,
3938 [0x61, 0x62, 0x63]
3939 );
3940
3941 for source in [
3942 "'abc'",
3943 "'😀'",
3944 "'\\u{1F600}'",
3945 "identifier",
3946 "𐐀",
3947 r"\u{10400}",
3948 ] {
3949 let limit = if source == "'abc'" { 2 } else { 1 };
3950 let error = Lexer::new(source)
3951 .with_string_limit(limit)
3952 .next_token()
3953 .unwrap_err();
3954 assert_eq!(error.kind, LexErrorKind::StringTooLong, "{source}");
3955 assert_eq!(error.message, "string too long");
3956 }
3957
3958 assert!(Lexer::new("'😀'").with_string_limit(2).next_token().is_ok());
3959 assert_eq!(
3960 Lexer::new("#a")
3961 .with_string_limit(1)
3962 .next_token()
3963 .unwrap_err()
3964 .kind,
3965 LexErrorKind::StringTooLong
3966 );
3967 assert!(Lexer::new("#a").with_string_limit(2).next_token().is_ok());
3968 assert!(Lexer::new("𐐀").with_string_limit(2).next_token().is_ok());
3969 assert!(
3970 Lexer::new(r"\u{10400}")
3971 .with_string_limit(2)
3972 .next_token()
3973 .is_ok()
3974 );
3975 assert!(
3976 Lexer::new("'\\u{1F600}'")
3977 .with_string_limit(2)
3978 .next_token()
3979 .is_ok()
3980 );
3981 assert!(
3982 Lexer::new("'a\\\nb'")
3983 .with_string_limit(2)
3984 .next_token()
3985 .is_ok()
3986 );
3987
3988 let overflow_first = Lexer::new("'ab\\xZ0'")
3989 .with_string_limit(1)
3990 .next_token()
3991 .unwrap_err();
3992 assert_eq!(overflow_first.kind, LexErrorKind::StringTooLong);
3993 let syntax_first = Lexer::new("'\\xZ0ab'")
3994 .with_string_limit(1)
3995 .next_token()
3996 .unwrap_err();
3997 assert_eq!(syntax_first.kind, LexErrorKind::InvalidEscape);
3998 }
3999
4000 #[test]
4001 fn ascii_string_runs_keep_overflow_and_unicode_cursor_positions() {
4002 for prefix in ["", "abc", "\\u{1f600}", "é", "\\n"] {
4003 let source = format!("'{prefix}abcdefghijklmnopqrstuvwxyz\\xZ'");
4004 let decoded_prefix = match prefix {
4005 "" => 0,
4006 "abc" => 3,
4007 "\\u{1f600}" => 2,
4008 _ => 1,
4009 };
4010 let error = Lexer::new(&source)
4011 .with_string_limit(decoded_prefix + 8)
4012 .next_token()
4013 .unwrap_err();
4014 assert_eq!(error.kind, LexErrorKind::StringTooLong);
4015 assert_eq!(error.span.end.byte_offset, 1 + prefix.len() + 9);
4016 }
4017 let source = "'abcédef\u{2028}ghi' next";
4018 let mut lexer = Lexer::new(source);
4019 let token = lexer.next_token().unwrap();
4020 assert_eq!(token.span.end.line, 2);
4021 assert_eq!(token.span.end.column, 5);
4022 assert_eq!(
4023 lexer.decode_string_literal(token.span.start).unwrap().utf16,
4024 "abcédef\u{2028}ghi".encode_utf16().collect::<Vec<_>>()
4025 );
4026 let next = lexer.next_token().unwrap();
4027 assert_eq!(next.span.start.line, 2);
4028 assert_eq!(next.span.start.column, 6);
4029 }
4030
4031 #[test]
4032 fn template_raw_and_cooked_values_share_the_checked_limit() {
4033 let lexer = Lexer::new("`\\u{1F600}`").with_string_limit(9);
4034 let token = lexer.clone().next_token().unwrap();
4035 let TokenKind::Template(_) = token.kind else {
4036 panic!("expected Template token");
4037 };
4038 assert_eq!(
4039 lexer
4040 .decode_template_raw_value(token.span.start, true)
4041 .unwrap()
4042 .utf16
4043 .len(),
4044 9
4045 );
4046 assert_eq!(
4047 lexer
4048 .decode_template_cooked_value(token.span.start, true)
4049 .unwrap()
4050 .utf16,
4051 [0xd83d, 0xde00]
4052 );
4053
4054 let raw_overflow = Lexer::new("`\\u{1F600}`")
4055 .with_string_limit(8)
4056 .next_token()
4057 .unwrap_err();
4058 assert_eq!(raw_overflow.kind, LexErrorKind::StringTooLong);
4059
4060 let invalid_cooked_still_checks_raw = Lexer::new("`\\8`")
4061 .with_string_limit(1)
4062 .next_token()
4063 .unwrap_err();
4064 assert_eq!(
4065 invalid_cooked_still_checks_raw.kind,
4066 LexErrorKind::StringTooLong
4067 );
4068
4069 assert!(
4070 Lexer::new("`a\r\nb`")
4071 .with_string_limit(3)
4072 .next_token()
4073 .is_ok()
4074 );
4075 assert_eq!(
4076 Lexer::new("`a\r\nb`")
4077 .with_string_limit(2)
4078 .next_token()
4079 .unwrap_err()
4080 .kind,
4081 LexErrorKind::StringTooLong
4082 );
4083 }
4084
4085 #[test]
4086 fn template_raw_overflow_precedes_later_escape_and_eof_errors() {
4087 let raw_before_eof = Lexer::new("`\\n")
4091 .with_string_limit(1)
4092 .next_token()
4093 .unwrap_err();
4094 assert_eq!(raw_before_eof.kind, LexErrorKind::StringTooLong);
4095 assert_eq!(raw_before_eof.message, "string too long");
4096
4097 let exact_raw_then_eof = Lexer::new("`\\n")
4098 .with_string_limit(2)
4099 .next_token()
4100 .unwrap_err();
4101 assert_eq!(exact_raw_then_eof.kind, LexErrorKind::UnterminatedTemplate);
4102
4103 let before_later_malformed = Lexer::new("`\\n\\8`")
4106 .with_string_limit(1)
4107 .next_token()
4108 .unwrap_err();
4109 assert_eq!(before_later_malformed.kind, LexErrorKind::StringTooLong);
4110
4111 let invalid_then_raw_before_eof = Lexer::new("`\\8a")
4114 .with_string_limit(2)
4115 .next_token()
4116 .unwrap_err();
4117 assert_eq!(
4118 invalid_then_raw_before_eof.kind,
4119 LexErrorKind::StringTooLong
4120 );
4121 }
4122
4123 #[test]
4124 fn hashbang_and_comments_feed_line_terminator_metadata() {
4125 let source = "#!/usr/bin/env qjs\nconst a = 1 // comment\n++a";
4126 let tokens = Lexer::new(source).tokenize().unwrap();
4127 assert!(tokens[0].line_terminator_before);
4128 let increment = tokens
4129 .iter()
4130 .find(|token| matches!(token.kind, TokenKind::Punctuator(Punctuator::Increment)))
4131 .unwrap();
4132 assert!(increment.line_terminator_before);
4133 }
4134
4135 #[test]
4136 fn quickjs_module_detection_keeps_the_raw_upstream_heuristic() {
4137 for source in [
4138 "export const answer = 42",
4139 "#!/usr/bin/env qjs\nexport const answer = 42",
4140 "/* leading */ import answer from './answer.js'",
4141 r"export\u0061 = 1",
4142 r"import\u0061 = 1",
4143 ] {
4144 assert!(quickjs_detect_module(source), "{source:?}");
4145 }
4146 for source in [
4147 "import('./answer.js')",
4148 "import . meta",
4149 "import.1",
4150 "// import at EOF",
4151 "// import\u{2028}export const answer = 42",
4152 "//\0\nexport const answer = 42",
4153 "/*\0*/export const answer = 42",
4154 "importπ",
4155 "const importValue = 42",
4156 "/* unterminated import",
4157 ] {
4158 assert!(!quickjs_detect_module(source), "{source:?}");
4159 }
4160 assert!(quickjs_detect_module("import /* unterminated"));
4161 assert!(quickjs_detect_module("import /*\0*/ ("));
4162 }
4163
4164 #[test]
4165 fn quickjs_module_detection_preserves_raw_bytes() {
4166 for source in [
4167 b"/*\x80*/export const answer = 42".as_slice(),
4168 b"//\xff\nimport answer from './answer.js'",
4169 b"#!\x80\xff\0ignored\nexport const answer = 42",
4170 b"\xc2\xa0export const answer = 42",
4171 b"import\x80",
4172 b"/*\xed\xa0\x80*/export const answer = 42",
4173 ] {
4174 assert!(quickjs_detect_module_bytes(source), "{source:?}");
4175 }
4176 for source in [
4177 b"/*\x80\0*/export const answer = 42".as_slice(),
4178 b"//\x80\0\nexport const answer = 42",
4179 b"\x80/* ignored */export const answer = 42",
4180 b"import\xcf\x80",
4181 b"import/*\x80*/('./answer.js')",
4182 b"import/*\xff*/.meta",
4183 ] {
4184 assert!(!quickjs_detect_module_bytes(source), "{source:?}");
4185 }
4186 }
4187
4188 #[test]
4189 fn annex_b_html_comments_are_explicitly_option_gated() {
4190 let disabled = Lexer::new("<!-- ignored").tokenize().unwrap();
4191 assert!(matches!(
4192 disabled[..4],
4193 [
4194 Token {
4195 kind: TokenKind::Punctuator(Punctuator::Less),
4196 ..
4197 },
4198 Token {
4199 kind: TokenKind::Punctuator(Punctuator::Not),
4200 ..
4201 },
4202 Token {
4203 kind: TokenKind::Punctuator(Punctuator::Decrement),
4204 ..
4205 },
4206 Token {
4207 kind: TokenKind::Identifier(_),
4208 ..
4209 }
4210 ]
4211 ));
4212
4213 let options = LexerOptions {
4214 allow_html_comments: true,
4215 ..LexerOptions::default()
4216 };
4217 let enabled = Lexer::with_options("value<!-- ignored\nnext", options)
4218 .tokenize()
4219 .unwrap();
4220 let identifiers = enabled
4221 .iter()
4222 .filter_map(|token| match &token.kind {
4223 TokenKind::Identifier(identifier) => Some(identifier.raw),
4224 _ => None,
4225 })
4226 .collect::<Vec<_>>();
4227 assert_eq!(identifiers, ["value", "next"]);
4228 assert!(enabled[1].line_terminator_before);
4229 }
4230
4231 #[test]
4232 fn annex_b_html_close_comment_requires_a_line_boundary() {
4233 let options = LexerOptions {
4234 allow_html_comments: true,
4235 ..LexerOptions::default()
4236 };
4237
4238 for separator in ['\n', '\r', '\u{2028}', '\u{2029}'] {
4239 let source = format!("head{separator}--> ignored{separator}tail");
4240 let tokens = Lexer::with_options(&source, options).tokenize().unwrap();
4241 let identifiers = tokens
4242 .iter()
4243 .filter_map(|token| match &token.kind {
4244 TokenKind::Identifier(identifier) => Some(identifier.raw),
4245 _ => None,
4246 })
4247 .collect::<Vec<_>>();
4248 assert_eq!(identifiers, ["head", "tail"], "{separator:?}");
4249 assert!(tokens[1].line_terminator_before, "{separator:?}");
4250 }
4251
4252 for source in [
4253 "--> ignored\nvalue",
4254 " --> ignored\nvalue",
4255 "/* leading */ --> ignored\nvalue",
4256 "0/*\n*/--> ignored\nvalue",
4257 ] {
4258 let tokens = Lexer::with_options(source, options)
4259 .tokenize()
4260 .unwrap_or_else(|error| {
4261 panic!("HTML close comment failed for {source:?}: {error}")
4262 });
4263 assert!(
4264 !tokens.iter().any(|token| matches!(
4265 token.kind,
4266 TokenKind::Punctuator(Punctuator::Decrement | Punctuator::Greater)
4267 )),
4268 "HTML close comment was tokenized for {source:?}"
4269 );
4270 let identifiers = tokens
4271 .iter()
4272 .filter_map(|token| match &token.kind {
4273 TokenKind::Identifier(identifier) => Some(identifier.raw),
4274 _ => None,
4275 })
4276 .collect::<Vec<_>>();
4277 assert_eq!(identifiers, ["value"], "{source:?}");
4278 }
4279
4280 let postfix = Lexer::with_options("count-->0", options)
4281 .tokenize()
4282 .unwrap();
4283 assert!(matches!(
4284 postfix[..4],
4285 [
4286 Token {
4287 kind: TokenKind::Identifier(_),
4288 ..
4289 },
4290 Token {
4291 kind: TokenKind::Punctuator(Punctuator::Decrement),
4292 ..
4293 },
4294 Token {
4295 kind: TokenKind::Punctuator(Punctuator::Greater),
4296 ..
4297 },
4298 Token {
4299 kind: TokenKind::Number(_),
4300 ..
4301 }
4302 ]
4303 ));
4304
4305 let no_line_boundary = Lexer::with_options("; --> ignored", options)
4306 .tokenize()
4307 .unwrap();
4308 assert!(matches!(
4309 no_line_boundary[1..3],
4310 [
4311 Token {
4312 kind: TokenKind::Punctuator(Punctuator::Decrement),
4313 ..
4314 },
4315 Token {
4316 kind: TokenKind::Punctuator(Punctuator::Greater),
4317 ..
4318 }
4319 ]
4320 ));
4321 }
4322
4323 #[test]
4324 fn unicode_line_terminators_after_identifiers_feed_asi_metadata() {
4325 let tokens = Lexer::new("value\u{2028}++next\u{2029}--last")
4326 .tokenize()
4327 .unwrap();
4328 let updates = tokens
4329 .iter()
4330 .filter(|token| {
4331 matches!(
4332 token.kind,
4333 TokenKind::Punctuator(Punctuator::Increment | Punctuator::Decrement)
4334 )
4335 })
4336 .collect::<Vec<_>>();
4337 assert_eq!(updates.len(), 2);
4338 assert!(updates.iter().all(|token| token.line_terminator_before));
4339 }
4340
4341 #[test]
4342 fn quickjs_simple_lookahead_preserves_its_block_comment_unicode_quirk() {
4343 for source in [
4344 "\nmethod",
4345 "\rmethod",
4346 "\u{2028}method",
4347 "\u{2029}method",
4348 "/*\n*/method",
4349 "/*\r*/method",
4350 "// comment at eof",
4351 ] {
4352 assert!(
4353 quickjs_simple_lookahead_has_line_terminator(source),
4354 "{source:?}"
4355 );
4356 }
4357 for source in [
4358 " method",
4359 "\u{00a0}method",
4360 "/* no break */method",
4361 "/*\u{2028}*/method",
4362 "/*\u{2029}*/method",
4363 "<!-- comment\nmethod",
4364 ] {
4365 assert!(
4366 !quickjs_simple_lookahead_has_line_terminator(source),
4367 "{source:?}"
4368 );
4369 }
4370 }
4371
4372 #[test]
4373 fn quickjs_simple_of_lookahead_keeps_raw_scanner_boundaries() {
4374 for source in [
4375 "of",
4376 " of",
4377 "\nof",
4378 "// comment\nof",
4379 "/* comment */of",
4380 r"of\u0061",
4381 "of<",
4382 ] {
4383 assert!(quickjs_simple_lookahead_is_of(source), "{source:?}");
4384 }
4385 for source in [
4386 "other",
4387 "ofx",
4388 "of\u{200c}",
4389 "of\u{200d}",
4390 "<!-- comment\nof",
4391 "--> comment\nof",
4392 "// comment\u{2028}of",
4393 ] {
4394 assert!(!quickjs_simple_lookahead_is_of(source), "{source:?}");
4395 }
4396 }
4397
4398 #[test]
4399 fn unterminated_constructs_have_readable_positions() {
4400 let comment = Lexer::new(" \n/* missing").next_token().unwrap_err();
4401 assert_eq!(comment.kind, LexErrorKind::UnterminatedComment);
4402 assert_eq!(comment.span.start.line, 2);
4403 assert_eq!(comment.span.start.column, 1);
4404 assert!(comment.to_string().starts_with("2:1"));
4405
4406 let template_source = format!("{TEMPLATE_QUOTE}missing");
4407 assert_eq!(
4408 Lexer::new(&template_source).next_token().unwrap_err().kind,
4409 LexErrorKind::UnterminatedTemplate
4410 );
4411 }
4412
4413 #[test]
4414 fn iterator_yields_eof_once_and_then_fuses() {
4415 let mut lexer = Lexer::new("");
4416 let eof = lexer.next().unwrap().unwrap();
4417 assert!(matches!(eof.kind, TokenKind::Eof));
4418 assert!(lexer.next().is_none());
4419 assert!(lexer.next().is_none());
4420 }
4421}