diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index a03d2d8e466c4..413b8bcd05127 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -159,18 +159,22 @@ pub struct RegExp<'a> { /// This pattern may or may not be parsed. #[ast] #[derive(Debug)] -#[generate_derive(CloneIn, Dummy, TakeIn, ESTree)] -#[estree(via = RegExpPatternConverter)] -pub enum RegExpPattern<'a> { - /// Unparsed pattern. Contains string slice of the pattern. - /// Pattern was not parsed, so may be valid or invalid. - Raw(&'a str) = 0, - /// An invalid pattern. Contains string slice of the pattern. - /// Pattern was parsed and found to be invalid. - Invalid(&'a str) = 1, - /// A parsed pattern. Read [Pattern] for more details. - /// Pattern was parsed and found to be valid. - Pattern(Box<'a, Pattern<'a>>) = 2, +#[generate_derive(CloneIn, Dummy, TakeIn, ContentEq, ESTree)] +#[estree(no_type, flatten)] +pub struct RegExpPattern<'a> { + /// The regexp's pattern as a string. + /// + /// If `pattern` is defined, `pattern` and `text` must be in sync. + /// i.e. If you alter the regexp by mutating `pattern`, you must regenerate `text` to match it, + /// using `format_atom!("{}", &pattern)`. + /// + /// `oxc_codegen` ignores `pattern` field, and prints `text`. + #[estree(rename = "pattern")] + pub text: Atom<'a>, + /// Parsed regexp pattern + #[content_eq(skip)] + #[estree(skip)] + pub pattern: Option>>, } bitflags! { diff --git a/crates/oxc_ast/src/ast_impl/literal.rs b/crates/oxc_ast/src/ast_impl/literal.rs index bd9c147750f01..ea03644c8e9d5 100644 --- a/crates/oxc_ast/src/ast_impl/literal.rs +++ b/crates/oxc_ast/src/ast_impl/literal.rs @@ -7,7 +7,6 @@ use std::{ use oxc_allocator::{Allocator, CloneIn, Dummy}; use oxc_data_structures::inline_string::InlineString; -use oxc_regular_expression::ast::Pattern; use oxc_span::ContentEq; use crate::ast::*; @@ -133,77 +132,7 @@ impl Display for BigIntLiteral<'_> { impl Display for RegExp<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "/{}/{}", self.pattern, self.flags) - } -} - -impl<'a> RegExpPattern<'a> { - /// Returns the number of characters in the pattern. - pub fn len(&self) -> usize { - match self { - Self::Raw(it) | Self::Invalid(it) => it.len(), - Self::Pattern(it) => it.span.size() as usize, - } - } - - /// Returns `true` if the pattern is empty (i.e. has a - /// [len](RegExpPattern::len) of `0`). - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the string as this regular expression would appear in source code. - pub fn source_text(&self, source_text: &'a str) -> Cow { - match self { - Self::Raw(raw) | Self::Invalid(raw) => Cow::Borrowed(raw), - Self::Pattern(pat) if pat.span.is_unspanned() => Cow::Owned(pat.to_string()), - Self::Pattern(pat) => Cow::Borrowed(pat.span.source_text(source_text)), - } - } - - /// # Panics - /// If `self` is anything but `RegExpPattern::Pattern`. - pub fn require_pattern(&self) -> &Pattern<'a> { - if let Some(it) = self.as_pattern() { - it - } else { - unreachable!( - "Required `{}` to be `{}`", - stringify!(RegExpPattern), - stringify!(Pattern) - ); - } - } - - /// Flatten this regular expression into a compiled [`Pattern`], returning - /// [`None`] if the pattern is invalid or not parsed. - pub fn as_pattern(&self) -> Option<&Pattern<'a>> { - if let Self::Pattern(it) = self { Some(it.as_ref()) } else { None } - } -} - -impl ContentEq for RegExpPattern<'_> { - fn content_eq(&self, other: &Self) -> bool { - let self_str = match self { - Self::Raw(s) | Self::Invalid(s) => *s, - Self::Pattern(p) => &p.to_string(), - }; - - let other_str = match other { - Self::Raw(s) | Self::Invalid(s) => *s, - Self::Pattern(p) => &p.to_string(), - }; - - self_str == other_str - } -} - -impl Display for RegExpPattern<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Raw(it) | Self::Invalid(it) => it.fmt(f), - Self::Pattern(it) => it.fmt(f), - } + write!(f, "/{}/{}", self.pattern.text, self.flags) } } diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index e667fc5445363..71857dfc0276c 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -796,6 +796,8 @@ const _: () = { assert!(size_of::() == 24); assert!(align_of::() == 8); + assert!(offset_of!(RegExpPattern, text) == 0); + assert!(offset_of!(RegExpPattern, pattern) == 16); assert!(size_of::() == 1); assert!(align_of::() == 1); @@ -2189,6 +2191,8 @@ const _: () = { assert!(size_of::() == 12); assert!(align_of::() == 4); + assert!(offset_of!(RegExpPattern, text) == 0); + assert!(offset_of!(RegExpPattern, pattern) == 8); assert!(size_of::() == 1); assert!(align_of::() == 1); diff --git a/crates/oxc_ast/src/generated/derive_clone_in.rs b/crates/oxc_ast/src/generated/derive_clone_in.rs index 143c6972aed26..6ba3c4a963539 100644 --- a/crates/oxc_ast/src/generated/derive_clone_in.rs +++ b/crates/oxc_ast/src/generated/derive_clone_in.rs @@ -4917,22 +4917,16 @@ impl<'new_alloc> CloneIn<'new_alloc> for RegExpPattern<'_> { type Cloned = RegExpPattern<'new_alloc>; fn clone_in(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { - match self { - Self::Raw(it) => RegExpPattern::Raw(CloneIn::clone_in(it, allocator)), - Self::Invalid(it) => RegExpPattern::Invalid(CloneIn::clone_in(it, allocator)), - Self::Pattern(it) => RegExpPattern::Pattern(CloneIn::clone_in(it, allocator)), + RegExpPattern { + text: CloneIn::clone_in(&self.text, allocator), + pattern: CloneIn::clone_in(&self.pattern, allocator), } } fn clone_in_with_semantic_ids(&self, allocator: &'new_alloc Allocator) -> Self::Cloned { - match self { - Self::Raw(it) => RegExpPattern::Raw(CloneIn::clone_in_with_semantic_ids(it, allocator)), - Self::Invalid(it) => { - RegExpPattern::Invalid(CloneIn::clone_in_with_semantic_ids(it, allocator)) - } - Self::Pattern(it) => { - RegExpPattern::Pattern(CloneIn::clone_in_with_semantic_ids(it, allocator)) - } + RegExpPattern { + text: CloneIn::clone_in_with_semantic_ids(&self.text, allocator), + pattern: CloneIn::clone_in_with_semantic_ids(&self.pattern, allocator), } } } diff --git a/crates/oxc_ast/src/generated/derive_content_eq.rs b/crates/oxc_ast/src/generated/derive_content_eq.rs index 8e89219f77e27..fbad8ee13c26c 100644 --- a/crates/oxc_ast/src/generated/derive_content_eq.rs +++ b/crates/oxc_ast/src/generated/derive_content_eq.rs @@ -1496,6 +1496,12 @@ impl ContentEq for RegExp<'_> { } } +impl ContentEq for RegExpPattern<'_> { + fn content_eq(&self, other: &Self) -> bool { + ContentEq::content_eq(&self.text, &other.text) + } +} + impl ContentEq for JSXElement<'_> { fn content_eq(&self, other: &Self) -> bool { ContentEq::content_eq(&self.opening_element, &other.opening_element) diff --git a/crates/oxc_ast/src/generated/derive_dummy.rs b/crates/oxc_ast/src/generated/derive_dummy.rs index 00d3b9ec7d4b4..fb1bfcb0eaf10 100644 --- a/crates/oxc_ast/src/generated/derive_dummy.rs +++ b/crates/oxc_ast/src/generated/derive_dummy.rs @@ -1635,7 +1635,7 @@ impl<'a> Dummy<'a> for RegExpPattern<'a> { /// /// Does not allocate any data into arena. fn dummy(allocator: &'a Allocator) -> Self { - Self::Raw(Dummy::dummy(allocator)) + Self { text: Dummy::dummy(allocator), pattern: Dummy::dummy(allocator) } } } diff --git a/crates/oxc_ast/src/generated/derive_estree.rs b/crates/oxc_ast/src/generated/derive_estree.rs index 94c50f285dad3..57590d623a7b5 100644 --- a/crates/oxc_ast/src/generated/derive_estree.rs +++ b/crates/oxc_ast/src/generated/derive_estree.rs @@ -1973,7 +1973,7 @@ impl ESTree for RegExpLiteral<'_> { impl ESTree for RegExp<'_> { fn serialize(&self, serializer: S) { let mut state = serializer.serialize_struct(); - state.serialize_field("pattern", &self.pattern); + state.serialize_field("pattern", &self.pattern.text); state.serialize_field("flags", &self.flags); state.end(); } @@ -1981,7 +1981,9 @@ impl ESTree for RegExp<'_> { impl ESTree for RegExpPattern<'_> { fn serialize(&self, serializer: S) { - crate::serialize::RegExpPatternConverter(self).serialize(serializer) + let mut state = serializer.serialize_struct(); + state.serialize_field("pattern", &self.text); + state.end(); } } diff --git a/crates/oxc_ast/src/serialize.rs b/crates/oxc_ast/src/serialize.rs index df8bb30215c3a..54e3f44fd0914 100644 --- a/crates/oxc_ast/src/serialize.rs +++ b/crates/oxc_ast/src/serialize.rs @@ -470,16 +470,6 @@ impl ESTree for RegExpLiteralValue<'_, '_> { } } -#[ast_meta] -#[estree(ts_type = "string")] -pub struct RegExpPatternConverter<'a, 'b>(pub &'b RegExpPattern<'a>); - -impl ESTree for RegExpPatternConverter<'_, '_> { - fn serialize(&self, serializer: S) { - self.0.to_string().serialize(serializer); - } -} - #[ast_meta] #[estree( ts_type = "string", diff --git a/crates/oxc_codegen/src/gen.rs b/crates/oxc_codegen/src/gen.rs index 8ba728dd08880..4cd9888f42e60 100644 --- a/crates/oxc_codegen/src/gen.rs +++ b/crates/oxc_codegen/src/gen.rs @@ -1,7 +1,7 @@ -use std::borrow::Cow; use std::ops::Not; use cow_utils::CowUtils; + use oxc_ast::ast::*; use oxc_span::GetSpan; use oxc_syntax::{ @@ -1353,19 +1353,21 @@ impl GenExpr for BigIntLiteral<'_> { impl Gen for RegExpLiteral<'_> { fn r#gen(&self, p: &mut Codegen, _ctx: Context) { p.add_source_mapping(self.span); - let last = p.last_byte(); - let pattern_text = p.source_text.map_or_else( - || Cow::Owned(self.regex.pattern.to_string()), - |src| self.regex.pattern.source_text(src), - ); // Avoid forming a single-line comment or "(parent_node: &AstNode<'a>) -> Option { - Some(Cow::Owned(format!("/{}/{}", regex.regex.pattern, regex.regex.flags))) - } + PropertyKey::RegExpLiteral(regex) => Some(Cow::Owned(regex.regex.to_string())), PropertyKey::BigIntLiteral(bigint) => Some(Cow::Borrowed(bigint.raw.as_str())), PropertyKey::TemplateLiteral(template) => { if template.expressions.is_empty() && template.quasis.len() == 1 { diff --git a/crates/oxc_linter/src/rules/eslint/no_div_regex.rs b/crates/oxc_linter/src/rules/eslint/no_div_regex.rs index cbb9a31f14b94..2dc7c2e788ee6 100644 --- a/crates/oxc_linter/src/rules/eslint/no_div_regex.rs +++ b/crates/oxc_linter/src/rules/eslint/no_div_regex.rs @@ -38,7 +38,7 @@ declare_oxc_lint!( impl Rule for NoDivRegex { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::RegExpLiteral(lit) = node.kind() { - let Some(pattern) = lit.regex.pattern.as_pattern() else { return }; + let Some(pattern) = &lit.regex.pattern.pattern else { return }; if pattern .body .body diff --git a/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs b/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs index f508197c96fba..3066e8b34838a 100644 --- a/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs +++ b/crates/oxc_linter/src/rules/eslint/no_empty_character_class.rs @@ -39,14 +39,12 @@ declare_oxc_lint!( impl Rule for NoEmptyCharacterClass { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { if let AstKind::RegExpLiteral(lit) = node.kind() { - let Some(pattern) = lit.regex.pattern.as_pattern() else { + let Some(pattern) = &lit.regex.pattern.pattern else { return; }; // Skip if the pattern doesn't contain a `[` or `]` character - if memchr2(b'[', b']', lit.regex.pattern.source_text(ctx.source_text()).as_bytes()) - .is_none() - { + if memchr2(b'[', b']', lit.regex.pattern.text.as_bytes()).is_none() { return; } diff --git a/crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs b/crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs index ac7bf42f047fb..322fee980d1d0 100644 --- a/crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs +++ b/crates/oxc_linter/src/rules/eslint/no_regex_spaces.rs @@ -51,7 +51,7 @@ impl Rule for NoRegexSpaces { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match node.kind() { AstKind::RegExpLiteral(lit) => { - if let Some(span) = Self::find_literal_to_report(lit, ctx) { + if let Some(span) = Self::find_literal_to_report(lit) { ctx.diagnostic(no_regex_spaces_diagnostic(span)); // /a b/ } } @@ -73,14 +73,13 @@ impl Rule for NoRegexSpaces { } impl NoRegexSpaces { - fn find_literal_to_report(literal: &RegExpLiteral, ctx: &LintContext) -> Option { - let pattern_text = literal.regex.pattern.source_text(ctx.source_text()); - let pattern_text = pattern_text.as_ref(); + fn find_literal_to_report(literal: &RegExpLiteral) -> Option { + let pattern_text = literal.regex.pattern.text.as_str(); if !Self::has_double_space(pattern_text) { return None; } - let pattern = literal.regex.pattern.as_pattern()?; + let pattern = literal.regex.pattern.pattern.as_deref()?; find_consecutive_spaces(pattern) } diff --git a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs index bead498030314..07348630f8da4 100644 --- a/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs +++ b/crates/oxc_linter/src/rules/eslint/no_useless_escape.rs @@ -77,10 +77,10 @@ impl Rule for NoUselessEscape { fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { match node.kind() { AstKind::RegExpLiteral(literal) - if literal.regex.pattern.len() + literal.regex.flags.iter().count() + if literal.regex.pattern.text.len() + literal.regex.flags.iter().count() != literal.span.size() as usize => { - if let Some(pattern) = literal.regex.pattern.as_pattern() { + if let Some(pattern) = &literal.regex.pattern.pattern { let mut finder = UselessEscapeFinder { useless_escape_spans: vec![], character_classes: vec![], diff --git a/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs b/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs index 6a36953011993..972a51afde864 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_hex_escape.rs @@ -95,7 +95,7 @@ impl Rule for NoHexEscape { }); } AstKind::RegExpLiteral(regex) => { - let Some(pattern) = regex.regex.pattern.as_pattern() else { + let Some(pattern) = ®ex.regex.pattern.pattern else { return; }; diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_string_replace_all.rs b/crates/oxc_linter/src/rules/unicorn/prefer_string_replace_all.rs index 04d07bb7b8b66..cc8005247888d 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_string_replace_all.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_string_replace_all.rs @@ -79,7 +79,7 @@ impl Rule for PreferStringReplaceAll { let pattern = &call_expr.arguments[0]; match method_name_str { "replaceAll" => { - if let Some(k) = get_pattern_replacement(pattern, ctx) { + if let Some(k) = get_pattern_replacement(pattern) { ctx.diagnostic_with_fix(string_literal(pattern.span(), &k), |fixer| { // foo.replaceAll(/hello world/g, bar) => foo.replaceAll("hello world", bar) fixer.replace(pattern.span(), format!("{k:?}")) @@ -115,10 +115,7 @@ fn is_reg_exp_with_global_flag<'a>(expr: &'a Argument<'a>) -> bool { false } -fn get_pattern_replacement<'a>( - expr: &'a Argument<'a>, - ctx: &LintContext<'a>, -) -> Option { +fn get_pattern_replacement<'a>(expr: &'a Argument<'a>) -> Option { let Argument::RegExpLiteral(reg_exp_literal) = expr else { return None; }; @@ -130,7 +127,8 @@ fn get_pattern_replacement<'a>( let pattern_terms = reg_exp_literal .regex .pattern - .as_pattern() + .pattern + .as_deref() .filter(|pattern| pattern.body.body.len() == 1) .and_then(|pattern| pattern.body.body.first().map(|it| &it.body))?; let is_simple_string = pattern_terms.iter().all(|term| matches!(term, Term::Character(_))); @@ -139,10 +137,7 @@ fn get_pattern_replacement<'a>( return None; } - let pattern_text = reg_exp_literal.regex.pattern.source_text(ctx.source_text()); - let pattern_text = pattern_text.as_ref(); - - Some(CompactStr::new(pattern_text)) + Some(CompactStr::new(reg_exp_literal.regex.pattern.text.as_str())) } #[test] diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs index c533a971443f4..baae3253db9af 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_string_starts_ends_with.rs @@ -80,10 +80,7 @@ impl Rule for PreferStringStartsEndsWith { return; }; - let pattern_text = regex.regex.pattern.source_text(ctx.source_text()); - let pattern_text = pattern_text.as_ref(); - - let Some(err_kind) = check_regex(regex, pattern_text) else { + let Some(err_kind) = check_regex(regex) else { return; }; @@ -149,7 +146,8 @@ enum ErrorKind { EndsWith(Vec), } -fn check_regex(regexp_lit: &RegExpLiteral, pattern_text: &str) -> Option { +fn check_regex(regexp_lit: &RegExpLiteral) -> Option { + let pattern_text = regexp_lit.regex.pattern.text.as_str(); if regexp_lit.regex.flags.intersects(RegExpFlags::M) || (regexp_lit.regex.flags.intersects(RegExpFlags::I | RegExpFlags::M) && is_useless_case_sensitive_regex_flag(pattern_text)) @@ -157,7 +155,8 @@ fn check_regex(regexp_lit: &RegExpLiteral, pattern_text: &str) -> Option 1 { return None; diff --git a/crates/oxc_linter/src/utils/regex.rs b/crates/oxc_linter/src/utils/regex.rs index 321f712901f5a..871b071d74c71 100644 --- a/crates/oxc_linter/src/utils/regex.rs +++ b/crates/oxc_linter/src/utils/regex.rs @@ -12,7 +12,7 @@ where { match node.kind() { AstKind::RegExpLiteral(reg) => { - if let Some(pat) = reg.regex.pattern.as_pattern() { + if let Some(pat) = ®.regex.pattern.pattern { cb(pat, reg.span); } } diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index 22a24727edc44..dfee4157a7f63 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -197,8 +197,7 @@ pub fn is_same_expression(left: &Expression, right: &Expression, ctx: &LintConte return left_num.raw == right_num.raw; } (Expression::RegExpLiteral(left_regexp), Expression::RegExpLiteral(right_regexp)) => { - return left_regexp.regex.pattern.source_text(ctx.source_text()) - == right_regexp.regex.pattern.source_text(ctx.source_text()) + return left_regexp.regex.pattern.text == right_regexp.regex.pattern.text && left_regexp.regex.flags == right_regexp.regex.flags; } (Expression::BooleanLiteral(left_bool), Expression::BooleanLiteral(right_bool)) => { diff --git a/crates/oxc_parser/src/js/expression.rs b/crates/oxc_parser/src/js/expression.rs index 86e0d1eb07dd6..6cb24cd792aca 100644 --- a/crates/oxc_parser/src/js/expression.rs +++ b/crates/oxc_parser/src/js/expression.rs @@ -355,28 +355,19 @@ impl<'a> ParserImpl<'a> { // Parse pattern if options is enabled and also flags are valid #[cfg(feature = "regular_expression")] - let pattern = { - (self.options.parse_regular_expression && !flags_error) - .then_some(()) - .map(|()| { - self.parse_regex_pattern(pattern_start, pattern_text, flags_start, flags_text) - }) - .map_or_else( - || RegExpPattern::Raw(pattern_text), - |pat| { - pat.map_or_else( - || RegExpPattern::Invalid(pattern_text), - RegExpPattern::Pattern, - ) - }, - ) + let pattern = if self.options.parse_regular_expression && !flags_error { + self.parse_regex_pattern(pattern_start, pattern_text, flags_start, flags_text) + } else { + None }; #[cfg(not(feature = "regular_expression"))] let pattern = { - let _ = (flags_start, flags_text, flags_error); - RegExpPattern::Raw(pattern_text) + let _ = (flags_text, flags_error); + None }; + let pattern = RegExpPattern { text: Atom::from(pattern_text), pattern }; + self.ast.reg_exp_literal( self.end_span(span), RegExp { pattern, flags }, diff --git a/crates/oxc_transformer/src/regexp/mod.rs b/crates/oxc_transformer/src/regexp/mod.rs index 4dc0f4dcb6576..c7f75182c4967 100644 --- a/crates/oxc_transformer/src/regexp/mod.rs +++ b/crates/oxc_transformer/src/regexp/mod.rs @@ -50,7 +50,7 @@ use oxc_regular_expression::ast::{ CharacterClass, CharacterClassContents, LookAroundAssertionKind, Pattern, Term, }; use oxc_semantic::ReferenceFlags; -use oxc_span::{Atom, SPAN, format_atom}; +use oxc_span::{Atom, SPAN}; use oxc_traverse::{Traverse, TraverseCtx}; use crate::TransformCtx; @@ -128,6 +128,7 @@ impl<'a> RegExp<'a, '_> { }; let regexp = regexp.as_mut(); + let pattern_text = regexp.regex.pattern.text; let flags = regexp.regex.flags; let has_unsupported_flags = flags.intersects(self.unsupported_flags); if !has_unsupported_flags { @@ -138,36 +139,33 @@ impl<'a> RegExp<'a, '_> { } let owned_pattern; - let pattern = match &mut regexp.regex.pattern { - RegExpPattern::Raw(raw) => { - #[expect(clippy::cast_possible_truncation)] - let pattern_len = raw.len() as u32; - let literal_span = regexp.span; - let pattern_span_start = literal_span.start + 1; // +1 to skip the opening `/` - let flags_span_start = pattern_span_start + pattern_len + 1; // +1 to skip the closing `/` - let flags_text = Span::new(flags_span_start, literal_span.end) - .source_text(self.ctx.source_text); - // Try to parse pattern - match try_parse_pattern( - raw, - pattern_span_start, - flags_text, - flags_span_start, - ctx, - ) { - Ok(pattern) => { - owned_pattern = Some(pattern); - owned_pattern.as_ref().unwrap() - } - Err(error) => { - regexp.regex.pattern = RegExpPattern::Invalid(raw); - self.ctx.error(error); - return; - } + let pattern = if let Some(pattern) = ®exp.regex.pattern.pattern { + pattern + } else { + #[expect(clippy::cast_possible_truncation)] + let pattern_len = pattern_text.len() as u32; + let literal_span = regexp.span; + let pattern_span_start = literal_span.start + 1; // +1 to skip the opening `/` + let flags_span_start = pattern_span_start + pattern_len + 1; // +1 to skip the closing `/` + let flags_text = + Span::new(flags_span_start, literal_span.end).source_text(self.ctx.source_text); + // Try to parse pattern + match try_parse_pattern( + pattern_text.as_str(), + pattern_span_start, + flags_text, + flags_span_start, + ctx, + ) { + Ok(pattern) => { + owned_pattern = Some(pattern); + owned_pattern.as_ref().unwrap() + } + Err(error) => { + self.ctx.error(error); + return; } } - RegExpPattern::Invalid(_) => return, - RegExpPattern::Pattern(pattern) => &**pattern, }; if !self.has_unsupported_regular_expression_pattern(pattern) { @@ -175,19 +173,13 @@ impl<'a> RegExp<'a, '_> { } } - let pattern_source = match ®exp.regex.pattern { - RegExpPattern::Raw(raw) => Atom::from(*raw), - RegExpPattern::Pattern(p) => format_atom!(ctx.ast.allocator, "{p}"), - RegExpPattern::Invalid(_) => return, - }; - let callee = { let symbol_id = ctx.scoping().find_binding(ctx.current_scope_id(), "RegExp"); ctx.create_ident_expr(SPAN, Atom::from("RegExp"), symbol_id, ReferenceFlags::read()) }; let arguments = ctx.ast.vec_from_array([ - Argument::from(ctx.ast.expression_string_literal(SPAN, pattern_source, None)), + Argument::from(ctx.ast.expression_string_literal(SPAN, pattern_text, None)), Argument::from(ctx.ast.expression_string_literal( SPAN, ctx.ast.atom(flags.to_inline_string().as_str()), diff --git a/napi/parser/generated/deserialize/js.js b/napi/parser/generated/deserialize/js.js index 953764279cf6e..15c59b8305b57 100644 --- a/napi/parser/generated/deserialize/js.js +++ b/napi/parser/generated/deserialize/js.js @@ -1109,11 +1109,17 @@ function deserializeRegExpLiteral(pos) { function deserializeRegExp(pos) { return { - pattern: deserializeRegExpPattern(pos), + pattern: deserializeStr(pos), flags: deserializeRegExpFlags(pos + 24), }; } +function deserializeRegExpPattern(pos) { + return { + pattern: deserializeStr(pos), + }; +} + function deserializeRegExpFlags(pos) { const flagBits = deserializeU8(pos); let flags = ''; @@ -3419,19 +3425,6 @@ function deserializeModuleExportName(pos) { } } -function deserializeRegExpPattern(pos) { - switch (uint8[pos]) { - case 0: - return deserializeStr(pos + 8); - case 1: - return deserializeStr(pos + 8); - case 2: - return deserializeBoxPattern(pos + 8); - default: - throw new Error(`Unexpected discriminant ${uint8[pos]} for RegExpPattern`); - } -} - function deserializeJSXElementName(pos) { switch (uint8[pos]) { case 0: @@ -5208,6 +5201,11 @@ function deserializeBoxPattern(pos) { return deserializePattern(uint32[pos >> 2]); } +function deserializeOptionBoxPattern(pos) { + if (uint32[pos >> 2] === 0 && uint32[(pos + 4) >> 2] === 0) return null; + return deserializeBoxPattern(pos); +} + function deserializeU8(pos) { return uint8[pos]; } diff --git a/napi/parser/generated/deserialize/ts.js b/napi/parser/generated/deserialize/ts.js index e022743ed6ea3..43e1b3ece7912 100644 --- a/napi/parser/generated/deserialize/ts.js +++ b/napi/parser/generated/deserialize/ts.js @@ -1262,11 +1262,17 @@ function deserializeRegExpLiteral(pos) { function deserializeRegExp(pos) { return { - pattern: deserializeRegExpPattern(pos), + pattern: deserializeStr(pos), flags: deserializeRegExpFlags(pos + 24), }; } +function deserializeRegExpPattern(pos) { + return { + pattern: deserializeStr(pos), + }; +} + function deserializeRegExpFlags(pos) { const flagBits = deserializeU8(pos); let flags = ''; @@ -3571,19 +3577,6 @@ function deserializeModuleExportName(pos) { } } -function deserializeRegExpPattern(pos) { - switch (uint8[pos]) { - case 0: - return deserializeStr(pos + 8); - case 1: - return deserializeStr(pos + 8); - case 2: - return deserializeBoxPattern(pos + 8); - default: - throw new Error(`Unexpected discriminant ${uint8[pos]} for RegExpPattern`); - } -} - function deserializeJSXElementName(pos) { switch (uint8[pos]) { case 0: @@ -5360,6 +5353,11 @@ function deserializeBoxPattern(pos) { return deserializePattern(uint32[pos >> 2]); } +function deserializeOptionBoxPattern(pos) { + if (uint32[pos >> 2] === 0 && uint32[(pos + 4) >> 2] === 0) return null; + return deserializeBoxPattern(pos); +} + function deserializeU8(pos) { return uint8[pos]; } diff --git a/tasks/benchmark/benches/lexer.rs b/tasks/benchmark/benches/lexer.rs index e5468cb1d37e4..ecf94398eaf48 100644 --- a/tasks/benchmark/benches/lexer.rs +++ b/tasks/benchmark/benches/lexer.rs @@ -119,9 +119,9 @@ impl SourceCleaner { impl<'a> Visit<'a> for SourceCleaner { fn visit_reg_exp_literal(&mut self, regexp: &RegExpLiteral<'a>) { - let RegExpPattern::Raw(pattern) = regexp.regex.pattern else { unreachable!() }; - let span = Span::sized(regexp.span.start, u32::try_from(pattern.len()).unwrap() + 2); - let text = convert_to_string(pattern); + let pattern_text = regexp.regex.pattern.text.as_str(); + let span = Span::sized(regexp.span.start, u32::try_from(pattern_text.len()).unwrap() + 2); + let text = convert_to_string(pattern_text); self.replace(span, text); } diff --git a/tasks/coverage/src/driver.rs b/tasks/coverage/src/driver.rs index 2d3ca695debe4..a5762ab080c69 100644 --- a/tasks/coverage/src/driver.rs +++ b/tasks/coverage/src/driver.rs @@ -197,7 +197,7 @@ impl<'a> Visit<'a> for CheckASTNodes<'a> { fn visit_reg_exp_literal(&mut self, literal: &RegExpLiteral<'a>) { walk::walk_reg_exp_literal(self, literal); - let Some(pattern) = literal.regex.pattern.as_pattern() else { + let Some(pattern) = &literal.regex.pattern.pattern else { return; }; let printed1 = pattern.to_string();