From 9b6b7bf98fa0d45c5b5a1cdfb42a1b350273e5eb Mon Sep 17 00:00:00 2001 From: Boshen Date: Fri, 30 Oct 2026 17:17:35 +0800 Subject: [PATCH] perf(regular_expression): advance validated Unicode escapes directly (#28514) Advance over decoded Unicode escape units in one step while retaining source offsets from the final unit span. AI-assisted (Codex). --- .../pattern_parser/pattern_parser_impl.rs | 11 +++------ .../src/parser/reader/reader_impl.rs | 9 ++++++++ .../tests/unicode_escape_spans.rs | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 crates/oxc_regular_expression/tests/unicode_escape_spans.rs diff --git a/crates/oxc_regular_expression/src/parser/pattern_parser/pattern_parser_impl.rs b/crates/oxc_regular_expression/src/parser/pattern_parser/pattern_parser_impl.rs index 8a0ff3f80b..b42bdfd5f1 100644 --- a/crates/oxc_regular_expression/src/parser/pattern_parser/pattern_parser_impl.rs +++ b/crates/oxc_regular_expression/src/parser/pattern_parser/pattern_parser_impl.rs @@ -2168,18 +2168,13 @@ impl<'a> PatternParser<'a> { let span_start = self.reader.offset(); match decode(self.reader.remaining_code_points(), unicode_mode) { UnicodeEscape::Value { code_point, length } => { - for _ in 0..length { - self.reader.advance(); - } + self.reader.advance_by(length); Ok(Some(code_point)) } UnicodeEscape::Overflow { digits } => { - self.reader.advance(); // u - self.reader.advance(); // { + self.reader.advance_by(2); // u{ let digits_start = self.reader.offset(); - for _ in 0..digits { - self.reader.advance(); - } + self.reader.advance_by(digits); Err(diagnostics::too_large_number_digits( self.span_factory.create(digits_start, self.reader.offset()), "hex", diff --git a/crates/oxc_regular_expression/src/parser/reader/reader_impl.rs b/crates/oxc_regular_expression/src/parser/reader/reader_impl.rs index a1a6ecffd0..e083f9f979 100644 --- a/crates/oxc_regular_expression/src/parser/reader/reader_impl.rs +++ b/crates/oxc_regular_expression/src/parser/reader/reader_impl.rs @@ -96,6 +96,15 @@ impl<'a> Reader<'a> { } } + /// Advance over units whose presence the caller has already checked. + pub fn advance_by(&mut self, count: usize) { + if count != 0 { + let index = self.index + count; + self.offset = self.units[index - 1].span.end; + self.index = index; + } + } + fn peek_nth(&self, n: usize) -> Option { let nth = self.index + n; self.units.get(nth).map(|cp| cp.value) diff --git a/crates/oxc_regular_expression/tests/unicode_escape_spans.rs b/crates/oxc_regular_expression/tests/unicode_escape_spans.rs new file mode 100644 index 0000000000..68a915673b --- /dev/null +++ b/crates/oxc_regular_expression/tests/unicode_escape_spans.rs @@ -0,0 +1,23 @@ +use oxc_allocator::Allocator; +use oxc_regular_expression::{ConstructorParser, Options, ast::Term}; + +#[test] +fn decoded_unicode_escapes_keep_source_spans() { + let allocator = Allocator::default(); + let source = r#""\\u{\x30\x30\x36\x31}b""#; + let parsed = ConstructorParser::new(&allocator, source, Some(r#""u""#), Options::default()) + .parse() + .unwrap(); + let terms = &parsed.body.body[0].body; + assert_eq!(terms.len(), 2); + let Term::Character(first) = &terms[0] else { + panic!("expected decoded character"); + }; + let Term::Character(second) = &terms[1] else { + panic!("expected trailing character"); + }; + assert_eq!(first.value, u32::from('a')); + assert_eq!(first.span.source_text(source), r"\\u{\x30\x30\x36\x31}"); + assert_eq!(second.value, u32::from('b')); + assert_eq!(second.span.source_text(source), "b"); +}