mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(regex): decode escaped capturing group names (#27156)
## Summary - Decode RegExp Unicode escapes in capture and named-reference names so equivalent spellings match, such as `/(?<Ꭰ>)\k<\u13A0>/`. - Share Unicode escape decoding with the capture pre-scan, and leave malformed names to the main parser so they retain their original diagnostics. - Update the TypeScript conformance snapshot to remove false invalid-reference diagnostics.
This commit is contained in:
1 parent
7a953767dd
commit
8f61129126
8 files changed
+338
-287
No files matched your search
@@ -10,7 +10,93 @@ pub use parser_impl::{ConstructorParser, LiteralParser};
|
||||
mod test {
|
||||
use oxc_allocator::Allocator;
|
||||
|
||||
use crate::{ConstructorParser, LiteralParser, Options};
|
||||
use crate::{ConstructorParser, LiteralParser, Options, ast::Term};
|
||||
|
||||
#[test]
|
||||
fn escaped_group_names() {
|
||||
let allocator = Allocator::default();
|
||||
|
||||
for (pattern, flags, expected_name) in [
|
||||
(r"(?<Ꭰ>)\k<\u13A0>", "", "Ꭰ"),
|
||||
(r"(?<\u13A0>)\k<Ꭰ>", "", "Ꭰ"),
|
||||
(r"(?<\u{13A0}>)\k<\u13A0>", "u", "Ꭰ"),
|
||||
(r"(?<𝒜>)\k<\uD835\uDC9C>", "u", "𝒜"),
|
||||
] {
|
||||
let parsed = LiteralParser::new(&allocator, pattern, Some(flags), Options::default())
|
||||
.parse()
|
||||
.unwrap_or_else(|error| panic!("/{pattern}/{flags}: {error}"));
|
||||
let terms = &parsed.body.body[0].body;
|
||||
let Term::CapturingGroup(group) = &terms[0] else {
|
||||
panic!("expected capturing group in /{pattern}/{flags}");
|
||||
};
|
||||
let Term::NamedReference(reference) = &terms[1] else {
|
||||
panic!("expected named reference in /{pattern}/{flags}");
|
||||
};
|
||||
assert_eq!(group.name.as_ref().unwrap().as_str(), expected_name);
|
||||
assert_eq!(reference.name.as_str(), expected_name);
|
||||
}
|
||||
|
||||
for pattern in [r"(?<Ꭰ>)(?<\u13A0>)", r"(?<\u13A0>)(?<Ꭰ>)"] {
|
||||
assert!(
|
||||
LiteralParser::new(&allocator, pattern, Some(""), Options::default())
|
||||
.parse()
|
||||
.is_err(),
|
||||
"/{pattern}/ should reject duplicate capture names"
|
||||
);
|
||||
}
|
||||
|
||||
let alternative_names = r"(?<Ꭰ>)|(?<\u13A0>)";
|
||||
assert!(
|
||||
LiteralParser::new(&allocator, alternative_names, Some(""), Options::default())
|
||||
.parse()
|
||||
.is_ok(),
|
||||
"duplicate capture names in separate alternatives are valid"
|
||||
);
|
||||
|
||||
for (pattern, expected) in [
|
||||
(r"(?<⽇>)(?<\u2F47>)", "Unterminated capturing group name"),
|
||||
(r"(?<_⽇>)(?<_\u2F47>)", "Unterminated capturing group name"),
|
||||
(r"(?<🌚>)(?<\u{1F31A}>)", "Invalid surrogate pair"),
|
||||
(r"(?<_🌚>)(?<_\u{1F31A}>)", "Invalid surrogate pair"),
|
||||
] {
|
||||
let error = LiteralParser::new(&allocator, pattern, Some(""), Options::default())
|
||||
.parse()
|
||||
.unwrap_err();
|
||||
assert!(error.to_string().contains(expected), "/{pattern}/: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escaped_group_names_in_constructor() {
|
||||
let allocator = Allocator::default();
|
||||
|
||||
for (source, expected_name) in [
|
||||
(r#""(?<\\u13A0>)\\k<\\u13A0>""#, "Ꭰ"),
|
||||
(r#""(?<\\u13A0>)\\k<Ꭰ>""#, "Ꭰ"),
|
||||
(r#""(?<\x61>)\\k<a>""#, "a"),
|
||||
] {
|
||||
let parsed = ConstructorParser::new(&allocator, source, None, Options::default())
|
||||
.parse()
|
||||
.unwrap_or_else(|error| panic!("{source}: {error}"));
|
||||
let terms = &parsed.body.body[0].body;
|
||||
let Term::CapturingGroup(group) = &terms[0] else {
|
||||
panic!("expected capturing group in {source}");
|
||||
};
|
||||
let Term::NamedReference(reference) = &terms[1] else {
|
||||
panic!("expected named reference in {source}");
|
||||
};
|
||||
assert_eq!(group.name.as_ref().unwrap().as_str(), expected_name);
|
||||
assert_eq!(reference.name.as_str(), expected_name);
|
||||
}
|
||||
|
||||
let duplicate = r#""(?<\\u13A0>)(?<Ꭰ>)""#;
|
||||
assert!(
|
||||
ConstructorParser::new(&allocator, duplicate, None, Options::default())
|
||||
.parse()
|
||||
.is_err(),
|
||||
"{duplicate} should reject duplicate capture names"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_pass() {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
use oxc_allocator::{Allocator, ArenaStringBuilder};
|
||||
use oxc_str::Str;
|
||||
|
||||
use crate::parser::{
|
||||
pattern_parser::{
|
||||
character,
|
||||
unicode_escape::{UnicodeEscape, decode},
|
||||
},
|
||||
reader::CodePoint,
|
||||
};
|
||||
use crate::surrogate_pair;
|
||||
|
||||
/// Decode a group name found by the capture pre-scan. `units` have already been decoded from
|
||||
/// a string or template literal, but may still contain RegExp Unicode escapes.
|
||||
/// Names without source escapes keep borrowing the source text. Malformed names return `None` so
|
||||
/// the pre-scan cannot report a duplicate before the main parser diagnoses the invalid name.
|
||||
/// <https://tc39.es/ecma262/multipage/text-processing.html#sec-static-semantics-capturinggroupname>
|
||||
pub(super) fn capturing_group_name<'a>(
|
||||
raw: &'a str,
|
||||
units: &[CodePoint],
|
||||
unicode_mode: bool,
|
||||
allocator: &'a Allocator,
|
||||
) -> Option<Str<'a>> {
|
||||
if units.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut name =
|
||||
raw.contains('\\').then(|| ArenaStringBuilder::with_capacity_in(raw.len(), allocator));
|
||||
let mut index = 0;
|
||||
while index < units.len() {
|
||||
let (mut value, mut next) = decode_code_point(units, index)?;
|
||||
|
||||
// In non-Unicode mode, RegExpIdentifierCodePoint combines raw surrogate pairs.
|
||||
// https://tc39.es/ecma262/multipage/text-processing.html#sec-static-semantics-regexpidentifiercodepoint
|
||||
if !unicode_mode
|
||||
&& units[index].value != '\\' as u32
|
||||
&& surrogate_pair::is_lead_surrogate(value)
|
||||
&& let Some(trail) = units.get(next).map(|unit| unit.value)
|
||||
&& surrogate_pair::is_trail_surrogate(trail)
|
||||
{
|
||||
value = surrogate_pair::combine_surrogate_pair(value, trail);
|
||||
next += 1;
|
||||
}
|
||||
|
||||
let valid = if index == 0 {
|
||||
character::is_identifier_start_char(value)
|
||||
} else {
|
||||
character::is_identifier_part_char(value)
|
||||
};
|
||||
if !valid {
|
||||
return None;
|
||||
}
|
||||
if let Some(name) = &mut name {
|
||||
name.push(char::from_u32(value)?);
|
||||
}
|
||||
index = next;
|
||||
}
|
||||
|
||||
Some(name.map_or_else(|| Str::from(raw), |name| Str::from(name.into_str())))
|
||||
}
|
||||
|
||||
fn decode_code_point(units: &[CodePoint], start: usize) -> Option<(u32, usize)> {
|
||||
let value = units.get(start)?.value;
|
||||
if value != '\\' as u32 {
|
||||
return Some((value, start + 1));
|
||||
}
|
||||
|
||||
match decode(&units[start + 1..], true) {
|
||||
UnicodeEscape::Value { code_point, length } => Some((code_point, start + 1 + length)),
|
||||
UnicodeEscape::Invalid | UnicodeEscape::Overflow { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
mod character;
|
||||
mod group_name;
|
||||
mod pattern_parser_impl;
|
||||
mod state;
|
||||
mod unicode_escape;
|
||||
mod unicode_property;
|
||||
|
||||
pub use pattern_parser_impl::PatternParser;
|
||||
@@ -1,11 +1,16 @@
|
||||
use oxc_allocator::{Allocator, ArenaBox, ArenaVec, GetAllocator};
|
||||
use oxc_allocator::{Allocator, ArenaBox, ArenaStringBuilder, ArenaVec, GetAllocator};
|
||||
use oxc_diagnostics::Result;
|
||||
use oxc_str::Str;
|
||||
|
||||
use crate::{
|
||||
ast, diagnostics,
|
||||
parser::{
|
||||
pattern_parser::{character, state::State, unicode_property},
|
||||
pattern_parser::{
|
||||
character,
|
||||
state::State,
|
||||
unicode_escape::{UnicodeEscape, decode},
|
||||
unicode_property,
|
||||
},
|
||||
reader::{EscapeKind, Reader},
|
||||
span_factory::SpanFactory,
|
||||
},
|
||||
@@ -58,11 +63,16 @@ impl<'a> PatternParser<'a> {
|
||||
|
||||
// [SS:EE] Pattern :: Disjunction
|
||||
// It is a Syntax Error if Pattern contains two or more GroupSpecifiers for which the CapturingGroupName of GroupSpecifier is the same.
|
||||
self.state.initialize_with_parsing(&mut self.reader).map_err(|offsets| {
|
||||
diagnostics::duplicated_capturing_group_names(
|
||||
offsets.iter().map(|&(start, end)| self.span_factory.create(start, end)).collect(),
|
||||
)
|
||||
})?;
|
||||
self.state.initialize_with_parsing(&mut self.reader, self.allocator).map_err(
|
||||
|offsets| {
|
||||
diagnostics::duplicated_capturing_group_names(
|
||||
offsets
|
||||
.iter()
|
||||
.map(|&(start, end)| self.span_factory.create(start, end))
|
||||
.collect(),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
|
||||
// [SS:EE] Pattern :: Disjunction
|
||||
// It is a Syntax Error if CountLeftCapturingParensWithin(Pattern) ≥ 2**32 - 1.
|
||||
@@ -540,6 +550,7 @@ impl<'a> PatternParser<'a> {
|
||||
if let Some(name) = self.consume_group_name()? {
|
||||
// [SS:EE] AtomEscape :: k GroupName
|
||||
// It is a Syntax Error if GroupSpecifiersThatMatch(GroupName) is empty.
|
||||
// https://tc39.es/ecma262/multipage/text-processing.html#sec-static-semantics-groupspecifiersthatmatch
|
||||
if !self.state.capturing_group_names.contains(name.as_str()) {
|
||||
let names: Vec<&str> =
|
||||
self.state.capturing_group_names.iter().map(Str::as_str).collect();
|
||||
@@ -1975,12 +1986,36 @@ impl<'a> PatternParser<'a> {
|
||||
fn consume_reg_exp_idenfigier_name(&mut self) -> Result<Option<Str<'a>>> {
|
||||
let span_start = self.reader.offset();
|
||||
|
||||
if self.consume_reg_exp_idenfigier_start()?.is_some() {
|
||||
while self.consume_reg_exp_idenfigier_part()?.is_some() {}
|
||||
return Ok(Some(self.reader.str(span_start, self.reader.offset())));
|
||||
let Some(mut cp) = self.consume_reg_exp_idenfigier_start()? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut decoded = None;
|
||||
let mut copied_until = span_start;
|
||||
let mut part_start = span_start;
|
||||
loop {
|
||||
let part_end = self.reader.offset();
|
||||
if self.reader.str(part_start, part_end).as_str().starts_with('\\') {
|
||||
let name =
|
||||
decoded.get_or_insert_with(|| ArenaStringBuilder::new_in(self.allocator));
|
||||
name.push_str(self.reader.str(copied_until, part_start).as_str());
|
||||
name.push(char::from_u32(cp).expect("validated group-name code point"));
|
||||
copied_until = part_end;
|
||||
}
|
||||
|
||||
part_start = part_end;
|
||||
let Some(next_cp) = self.consume_reg_exp_idenfigier_part()? else {
|
||||
break;
|
||||
};
|
||||
cp = next_cp;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
if let Some(mut name) = decoded {
|
||||
name.push_str(self.reader.str(copied_until, self.reader.offset()).as_str());
|
||||
Ok(Some(Str::from(name.into_str())))
|
||||
} else {
|
||||
Ok(Some(self.reader.str(span_start, self.reader.offset())))
|
||||
}
|
||||
}
|
||||
|
||||
// ```
|
||||
@@ -2113,76 +2148,35 @@ impl<'a> PatternParser<'a> {
|
||||
unicode_mode: bool,
|
||||
) -> Result<Option<u32>> {
|
||||
let span_start = self.reader.offset();
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
if self.reader.eat('u') {
|
||||
if unicode_mode {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
// HexLeadSurrogate + HexTrailSurrogate
|
||||
if let Some(lead_surrogate) = self
|
||||
.consume_fixed_hex_digits(4)
|
||||
.filter(|&cp| surrogate_pair::is_lead_surrogate(cp))
|
||||
&& self.reader.eat2('\\', 'u')
|
||||
&& let Some(trail_surrogate) = self
|
||||
.consume_fixed_hex_digits(4)
|
||||
.filter(|&cp| surrogate_pair::is_trail_surrogate(cp))
|
||||
{
|
||||
return Ok(Some(surrogate_pair::combine_surrogate_pair(
|
||||
lead_surrogate,
|
||||
trail_surrogate,
|
||||
)));
|
||||
match decode(self.reader.remaining_code_points(), unicode_mode) {
|
||||
UnicodeEscape::Value { code_point, length } => {
|
||||
for _ in 0..length {
|
||||
self.reader.advance();
|
||||
}
|
||||
self.reader.rewind(checkpoint);
|
||||
|
||||
// HexLeadSurrogate
|
||||
if let Some(lead_surrogate) = self
|
||||
.consume_fixed_hex_digits(4)
|
||||
.filter(|&cp| surrogate_pair::is_lead_surrogate(cp))
|
||||
{
|
||||
return Ok(Some(lead_surrogate));
|
||||
Ok(Some(code_point))
|
||||
}
|
||||
UnicodeEscape::Overflow { digits } => {
|
||||
self.reader.advance(); // u
|
||||
self.reader.advance(); // {
|
||||
let digits_start = self.reader.offset();
|
||||
for _ in 0..digits {
|
||||
self.reader.advance();
|
||||
}
|
||||
self.reader.rewind(checkpoint);
|
||||
|
||||
// HexTrailSurrogate
|
||||
if let Some(trail_surrogate) = self
|
||||
.consume_fixed_hex_digits(4)
|
||||
.filter(|&cp| surrogate_pair::is_trail_surrogate(cp))
|
||||
{
|
||||
return Ok(Some(trail_surrogate));
|
||||
Err(diagnostics::too_large_number_digits(
|
||||
self.span_factory.create(digits_start, self.reader.offset()),
|
||||
"hex",
|
||||
))
|
||||
}
|
||||
UnicodeEscape::Invalid => {
|
||||
if self.state.unicode_mode && self.reader.eat('u') {
|
||||
Err(diagnostics::invalid_unicode_escape_sequence(
|
||||
self.span_factory.create(span_start, self.reader.offset()),
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
self.reader.rewind(checkpoint);
|
||||
}
|
||||
|
||||
// HexNonSurrogate and Hex4Digits are the same
|
||||
if let Some(hex_digits) = self.consume_fixed_hex_digits(4) {
|
||||
return Ok(Some(hex_digits));
|
||||
}
|
||||
|
||||
// {CodePoint}
|
||||
if unicode_mode {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
if self.reader.eat('{')
|
||||
&& let Some(hex_digits) =
|
||||
self.consume_hex_digits()?.filter(|&cp| character::is_valid_unicode(cp))
|
||||
&& self.reader.eat('}')
|
||||
{
|
||||
return Ok(Some(hex_digits));
|
||||
}
|
||||
self.reader.rewind(checkpoint);
|
||||
}
|
||||
|
||||
if self.state.unicode_mode {
|
||||
return Err(diagnostics::invalid_unicode_escape_sequence(
|
||||
self.span_factory.create(span_start, self.reader.offset()),
|
||||
));
|
||||
}
|
||||
|
||||
self.reader.rewind(checkpoint);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// ```
|
||||
@@ -2302,31 +2296,6 @@ impl<'a> PatternParser<'a> {
|
||||
Some(cp)
|
||||
}
|
||||
|
||||
fn consume_hex_digits(&mut self) -> Result<Option<u32>> {
|
||||
let span_start = self.reader.offset();
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
let mut value: u32 = 0;
|
||||
while let Some(hex) = self.reader.peek().and_then(character::map_hex_digit) {
|
||||
// To prevent panic on overflow cases like `\u{FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF}`
|
||||
if let Some(v) = value.checked_mul(16).and_then(|v| v.checked_add(hex)) {
|
||||
value = v;
|
||||
self.reader.advance();
|
||||
} else {
|
||||
return Err(diagnostics::too_large_number_digits(
|
||||
self.span_factory.create(span_start, self.reader.offset()),
|
||||
"hex",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if self.reader.checkpoint() != checkpoint {
|
||||
return Ok(Some(value));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn consume_fixed_hex_digits(&mut self, len: usize) -> Option<u32> {
|
||||
let checkpoint = self.reader.checkpoint();
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_str::Str;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::parser::reader::Reader;
|
||||
use crate::parser::{pattern_parser::group_name::capturing_group_name, reader::Reader};
|
||||
|
||||
/// NOTE: Currently all of properties are read-only from outside of this module.
|
||||
/// Even inside of this module, it is not changed after initialized.
|
||||
@@ -32,6 +33,7 @@ impl<'a> State<'a> {
|
||||
pub fn initialize_with_parsing(
|
||||
&mut self,
|
||||
reader: &mut Reader<'a>,
|
||||
allocator: &'a Allocator,
|
||||
) -> Result<(), DuplicatedNamedCapturingGroupOffsets> {
|
||||
// PERF: Fast path, no `(` in the decoded units.
|
||||
// Provably no capturing group (named or unnamed) and no duplicate name.
|
||||
@@ -48,7 +50,8 @@ impl<'a> State<'a> {
|
||||
}
|
||||
|
||||
let checkpoint = reader.checkpoint();
|
||||
let (num_of_left_capturing_parens, capturing_group_names) = parse_capturing_groups(reader)?;
|
||||
let (num_of_left_capturing_parens, capturing_group_names) =
|
||||
parse_capturing_groups(reader, self.unicode_mode, allocator)?;
|
||||
reader.rewind(checkpoint);
|
||||
|
||||
// In Annex B, this is `false` by default.
|
||||
@@ -70,6 +73,8 @@ impl<'a> State<'a> {
|
||||
/// Returns: Result<(num_of_left_parens, capturing_group_names), duplicated_named_capturing_group_offsets>
|
||||
fn parse_capturing_groups<'a>(
|
||||
reader: &mut Reader<'a>,
|
||||
unicode_mode: bool,
|
||||
allocator: &'a Allocator,
|
||||
) -> Result<(u32, FxHashSet<Str<'a>>), DuplicatedNamedCapturingGroupOffsets> {
|
||||
// Count only normal CapturingGroup(named, unnamed)
|
||||
// (?<name>...), (...)
|
||||
@@ -122,6 +127,7 @@ fn parse_capturing_groups<'a>(
|
||||
// Collect capturing group names
|
||||
if reader.eat2('?', '<') {
|
||||
let span_start = reader.offset();
|
||||
let name_start = reader.checkpoint();
|
||||
while let Some(ch) = reader.peek() {
|
||||
if ch == '>' as u32 {
|
||||
break;
|
||||
@@ -130,8 +136,17 @@ fn parse_capturing_groups<'a>(
|
||||
}
|
||||
let span_end = reader.offset();
|
||||
|
||||
if reader.eat('>') {
|
||||
let group_name = reader.str(span_start, span_end);
|
||||
if reader.peek() == Some('>' as u32) {
|
||||
let group_name = capturing_group_name(
|
||||
reader.str(span_start, span_end).as_str(),
|
||||
reader.code_points_since(name_start),
|
||||
unicode_mode,
|
||||
allocator,
|
||||
);
|
||||
reader.advance();
|
||||
let Some(group_name) = group_name else {
|
||||
continue;
|
||||
};
|
||||
let alternative_path = tracker.get_alternative_path();
|
||||
|
||||
// Check for duplicates with existing groups
|
||||
@@ -260,8 +275,9 @@ mod tests {
|
||||
] {
|
||||
let mut reader = Reader::initialize(source_text, true, false).unwrap();
|
||||
|
||||
let allocator = Allocator::default();
|
||||
let (num_of_left_capturing_parens, capturing_group_names) =
|
||||
parse_capturing_groups(&mut reader).unwrap();
|
||||
parse_capturing_groups(&mut reader, true, &allocator).unwrap();
|
||||
|
||||
let actual = (num_of_left_capturing_parens, capturing_group_names.len());
|
||||
assert_eq!(expected, actual, "{source_text}");
|
||||
@@ -281,7 +297,11 @@ mod tests {
|
||||
] {
|
||||
let mut reader = Reader::initialize(source_text, true, false).unwrap();
|
||||
|
||||
assert!(parse_capturing_groups(&mut reader).is_err(), "{source_text}");
|
||||
let allocator = Allocator::default();
|
||||
assert!(
|
||||
parse_capturing_groups(&mut reader, true, &allocator).is_err(),
|
||||
"{source_text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::{
|
||||
parser::{pattern_parser::character, reader::CodePoint},
|
||||
surrogate_pair,
|
||||
};
|
||||
|
||||
pub(super) enum UnicodeEscape {
|
||||
Value {
|
||||
code_point: u32,
|
||||
length: usize,
|
||||
},
|
||||
Invalid,
|
||||
/// Number of valid digits consumed after `u{` before the overflowing digit.
|
||||
Overflow {
|
||||
digits: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Decode RegExpUnicodeEscapeSequence from cooked code points, beginning with `u`.
|
||||
/// <https://tc39.es/ecma262/multipage/text-processing.html#prod-RegExpUnicodeEscapeSequence>
|
||||
pub(super) fn decode(units: &[CodePoint], unicode_mode: bool) -> UnicodeEscape {
|
||||
if units.first().is_none_or(|unit| unit.value != 'u' as u32) {
|
||||
return UnicodeEscape::Invalid;
|
||||
}
|
||||
|
||||
if let Some(value) = fixed_hex_digits(units, 1) {
|
||||
if unicode_mode
|
||||
&& surrogate_pair::is_lead_surrogate(value)
|
||||
&& units.get(5).is_some_and(|unit| unit.value == '\\' as u32)
|
||||
&& units.get(6).is_some_and(|unit| unit.value == 'u' as u32)
|
||||
&& let Some(trail) = fixed_hex_digits(units, 7)
|
||||
&& surrogate_pair::is_trail_surrogate(trail)
|
||||
{
|
||||
return UnicodeEscape::Value {
|
||||
code_point: surrogate_pair::combine_surrogate_pair(value, trail),
|
||||
length: 11,
|
||||
};
|
||||
}
|
||||
return UnicodeEscape::Value { code_point: value, length: 5 };
|
||||
}
|
||||
|
||||
if unicode_mode && units.get(1).is_some_and(|unit| unit.value == '{' as u32) {
|
||||
let mut index = 2;
|
||||
let mut value = 0_u32;
|
||||
while let Some(digit) =
|
||||
units.get(index).and_then(|unit| character::map_hex_digit(unit.value))
|
||||
{
|
||||
let Some(next) = value.checked_mul(16).and_then(|value| value.checked_add(digit))
|
||||
else {
|
||||
return UnicodeEscape::Overflow { digits: index - 2 };
|
||||
};
|
||||
value = next;
|
||||
index += 1;
|
||||
}
|
||||
if index > 2
|
||||
&& units.get(index).is_some_and(|unit| unit.value == '}' as u32)
|
||||
&& character::is_valid_unicode(value)
|
||||
{
|
||||
return UnicodeEscape::Value { code_point: value, length: index + 1 };
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeEscape::Invalid
|
||||
}
|
||||
|
||||
fn fixed_hex_digits(units: &[CodePoint], start: usize) -> Option<u32> {
|
||||
let mut value = 0_u32;
|
||||
for unit in units.get(start..start + 4)? {
|
||||
value = value * 16 + character::map_hex_digit(unit.value)?;
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
@@ -75,6 +75,15 @@ impl<'a> Reader<'a> {
|
||||
(self.index, self.offset)
|
||||
}
|
||||
|
||||
/// Return the cooked code points consumed since `checkpoint`.
|
||||
pub fn code_points_since(&self, checkpoint: (usize, u32)) -> &[CodePoint] {
|
||||
&self.units[checkpoint.0..self.index]
|
||||
}
|
||||
|
||||
pub fn remaining_code_points(&self) -> &[CodePoint] {
|
||||
&self.units[self.index..]
|
||||
}
|
||||
|
||||
pub fn rewind(&mut self, checkpoint: (usize, u32)) {
|
||||
self.index = checkpoint.0;
|
||||
self.offset = checkpoint.1;
|
||||
|
||||
@@ -12388,60 +12388,6 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
╰────
|
||||
help: Valid group names are: foo
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:2:17]
|
||||
1 │ // U+13A0 CHEROKEE LETTER A: character inside BMP with both the Unicode properties ID_Start and ID_Continue
|
||||
2 │ /(?<Ꭰ>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
· ──────────
|
||||
3 │ /(?<\u13A0>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
╰────
|
||||
help: Valid group names are: Ꭰ
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:3:13]
|
||||
2 │ /(?<Ꭰ>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
3 │ /(?<\u13A0>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
· ─────
|
||||
4 │ /(?<\u{13A0}>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
╰────
|
||||
help: Valid group names are: \u13A0
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:4:15]
|
||||
3 │ /(?<\u13A0>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
4 │ /(?<\u{13A0}>)\k<Ꭰ>\k<\u13A0>\k<\u{13A0}>/;
|
||||
· ─────
|
||||
5 │
|
||||
╰────
|
||||
help: Valid group names are: \u{13A0}
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:6:19]
|
||||
5 │
|
||||
6 │ /(?<_Ꭰ>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
· ───────────
|
||||
7 │ /(?<_\u13A0>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
╰────
|
||||
help: Valid group names are: _Ꭰ
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:7:14]
|
||||
6 │ /(?<_Ꭰ>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
7 │ /(?<_\u13A0>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
· ──────
|
||||
8 │ /(?<_\u{13A0}>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
╰────
|
||||
help: Valid group names are: _\u13A0
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:8:16]
|
||||
7 │ /(?<_\u13A0>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
8 │ /(?<_\u{13A0}>)\k<_Ꭰ>\k<_\u13A0>\k<_\u{13A0}>/;
|
||||
· ──────
|
||||
9 │
|
||||
╰────
|
||||
help: Valid group names are: _\u{13A0}
|
||||
|
||||
× Invalid regular expression: Unterminated capturing group name
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:11:4]
|
||||
10 │ // U+19D4 NEW TAI LUE DIGIT FOUR: character inside BMP with only ID_Continue
|
||||
@@ -12469,87 +12415,6 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
╰────
|
||||
help: Use `\uXXXX` or `\u{XXXXX}` format for unicode escapes
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:15:19]
|
||||
14 │
|
||||
15 │ /(?<_᧔>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
· ───────────
|
||||
16 │ /(?<_\u19D4>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
╰────
|
||||
help: Valid group names are: _᧔
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:16:14]
|
||||
15 │ /(?<_᧔>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
16 │ /(?<_\u19D4>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
· ──────
|
||||
17 │ /(?<_\u{19D4}>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
╰────
|
||||
help: Valid group names are: _\u19D4
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:17:16]
|
||||
16 │ /(?<_\u19D4>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
17 │ /(?<_\u{19D4}>)\k<_᧔>\k<_\u19D4>\k<_\u{19D4}>/;
|
||||
· ──────
|
||||
18 │
|
||||
╰────
|
||||
help: Valid group names are: _\u{19D4}
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:20:19]
|
||||
19 │ // U+102A7 CARIAN LETTER A2: character outside BMP with both ID_Start and ID_Continue
|
||||
20 │ /(?<𐊧>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
· ─────────────
|
||||
21 │ /(?<\u{102A7}>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
╰────
|
||||
help: Valid group names are: 𐊧
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:21:16]
|
||||
20 │ /(?<𐊧>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
21 │ /(?<\u{102A7}>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
· ─────
|
||||
22 │ /(?<\uD800\uDEA7>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
╰────
|
||||
help: Valid group names are: \u{102A7}
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:22:19]
|
||||
21 │ /(?<\u{102A7}>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
22 │ /(?<\uD800\uDEA7>)\k<𐊧>\k<\u{102A7}>\k<\uD800\uDEA7>/;
|
||||
· ─────
|
||||
23 │
|
||||
╰────
|
||||
help: Valid group names are: \uD800\uDEA7
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:24:21]
|
||||
23 │
|
||||
24 │ /(?<_𐊧>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
· ──────────────
|
||||
25 │ /(?<_\u{102A7}>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
╰────
|
||||
help: Valid group names are: _𐊧
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:25:17]
|
||||
24 │ /(?<_𐊧>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
25 │ /(?<_\u{102A7}>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
· ──────
|
||||
26 │ /(?<_\uD800\uDEA7>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
╰────
|
||||
help: Valid group names are: _\u{102A7}
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:26:20]
|
||||
25 │ /(?<_\u{102A7}>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
26 │ /(?<_\uD800\uDEA7>)\k<_𐊧>\k<_\u{102A7}>\k<_\uD800\uDEA7>/;
|
||||
· ──────
|
||||
27 │
|
||||
╰────
|
||||
help: Valid group names are: _\uD800\uDEA7
|
||||
|
||||
× Invalid regular expression: Invalid surrogate pair
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:29:5]
|
||||
28 │ // U+1113D CHAKMA DIGIT SEVEN: character outside BMP with only ID_Continue
|
||||
@@ -12577,33 +12442,6 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
╰────
|
||||
help: Use `\uXXXX` or `\u{XXXXX}` format for unicode escapes
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:33:21]
|
||||
32 │
|
||||
33 │ /(?<_𑄽>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
· ──────────────
|
||||
34 │ /(?<_\u{1113D}>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
╰────
|
||||
help: Valid group names are: _𑄽
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:34:17]
|
||||
33 │ /(?<_𑄽>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
34 │ /(?<_\u{1113D}>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
· ──────
|
||||
35 │ /(?<_\uD804\uDD3D>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
╰────
|
||||
help: Valid group names are: _\u{1113D}
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:35:20]
|
||||
34 │ /(?<_\u{1113D}>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
35 │ /(?<_\uD804\uDD3D>)\k<_𑄽>\k<_\u{1113D}>\k<_\uD804\uDD3D>/;
|
||||
· ──────
|
||||
36 │
|
||||
╰────
|
||||
help: Valid group names are: _\uD804\uDD3D
|
||||
|
||||
× Invalid regular expression: Unterminated capturing group name
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:40:4]
|
||||
39 │ // U+2F47 KANGXI RADICAL SUN: character inside BMP without both properties
|
||||
@@ -12694,23 +12532,6 @@ Expect to Parse: tasks/coverage/typescript/tsc/testdata/tests/cases/conformance/
|
||||
╰────
|
||||
help: Use `\uXXXX` or `\u{XXXXX}` format for unicode escapes
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:60:23]
|
||||
59 │ // Cases with Unicode escapes and extended Unicode escapes mixed
|
||||
60 │ /(?<RegExp>)\k<RegExp>\k<\u{52}\u0065gE\u{78}p>\k<\u0052e\u0067\u{45}x\u{70}>/;
|
||||
· ─────────────────────────
|
||||
61 │ /(?<_RegExp>)\k<_RegExp>\k<_\u{52}\u0065gE\u{78}p>\k<_\u0052e\u0067\u{45}x\u{70}>/;
|
||||
╰────
|
||||
help: Valid group names are: RegExp
|
||||
|
||||
× Invalid regular expression: Invalid named reference
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionGroupNameUnicodeEscapes.ts:61:25]
|
||||
60 │ /(?<RegExp>)\k<RegExp>\k<\u{52}\u0065gE\u{78}p>\k<\u0052e\u0067\u{45}x\u{70}>/;
|
||||
61 │ /(?<_RegExp>)\k<_RegExp>\k<_\u{52}\u0065gE\u{78}p>\k<_\u0052e\u0067\u{45}x\u{70}>/;
|
||||
· ──────────────────────────
|
||||
╰────
|
||||
help: Valid group names are: _RegExp
|
||||
|
||||
× Invalid regular expression: Number is too large in braced quantifier
|
||||
╭─[typescript/tsc/testdata/tests/cases/compiler/regularExpressionQuantifierBounds1.ts:3:4]
|
||||
2 │ /a{7,8}/,
|
||||
|
||||
Reference in new issue
Block a user