diff --git a/crates/oxc_transformer/src/es2015/mod.rs b/crates/oxc_transformer/src/es2015/mod.rs index 5c64a1361f132..7ff2ccadb763f 100644 --- a/crates/oxc_transformer/src/es2015/mod.rs +++ b/crates/oxc_transformer/src/es2015/mod.rs @@ -1,5 +1,3 @@ mod shorthand_properties; -mod sticky_regex; pub use shorthand_properties::ShorthandProperties; -pub use sticky_regex::StickyRegex; diff --git a/crates/oxc_transformer/src/es2015/sticky_regex.rs b/crates/oxc_transformer/src/es2015/sticky_regex.rs deleted file mode 100644 index 294713d1a7d72..0000000000000 --- a/crates/oxc_transformer/src/es2015/sticky_regex.rs +++ /dev/null @@ -1,43 +0,0 @@ -use oxc_ast::{ast::*, AstBuilder}; -use oxc_span::{Atom, Span}; - -use std::rc::Rc; - -/// ES2015: Sticky Regex -/// -/// References: -/// * -/// * -pub struct StickyRegex<'a> { - ast: Rc>, -} - -impl<'a> StickyRegex<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } - } - - pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { - let Expression::RegExpLiteral(reg_literal) = expr else { return }; - if !reg_literal.regex.flags.contains(RegExpFlags::Y) { - return; - } - - let ident = IdentifierReference::new(Span::default(), Atom::from("RegExp")); - let callee = self.ast.identifier_expression(ident); - let pattern_literal = self - .ast - .string_literal(Span::default(), Atom::from(reg_literal.regex.pattern.as_str())); - let flags_literal = self - .ast - .string_literal(Span::default(), Atom::from(reg_literal.regex.flags.to_string())); - let pattern_literal = self.ast.literal_string_expression(pattern_literal); - let flags_literal = self.ast.literal_string_expression(flags_literal); - - let mut arguments = self.ast.new_vec_with_capacity(2); - arguments.push(Argument::Expression(pattern_literal)); - arguments.push(Argument::Expression(flags_literal)); - - *expr = self.ast.new_expression(Span::default(), callee, arguments, None); - } -} diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index c95c966aedde0..b7b1b3f4c83a7 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -14,11 +14,13 @@ mod es2021; mod es2022; mod options; mod react_jsx; +mod regexp; mod typescript; use oxc_allocator::Allocator; use oxc_ast::{ast::*, AstBuilder, VisitMut}; use oxc_span::SourceType; +use regexp::RegexpFlags; use std::rc::Rc; use es2015::ShorthandProperties; @@ -35,6 +37,7 @@ pub use crate::options::{ #[derive(Default)] pub struct Transformer<'a> { typescript: Option>, + regexp_flags: Option>, react_jsx: Option>, // es2022 es2022_class_static_block: Option>, @@ -46,7 +49,6 @@ pub struct Transformer<'a> { es2016_exponentiation_operator: Option>, // es2015 es2015_shorthand_properties: Option>, - es2015_sticky_regex: Option>, } impl<'a> Transformer<'a> { @@ -64,6 +66,9 @@ impl<'a> Transformer<'a> { if let Some(react_options) = options.react { t.react_jsx.replace(ReactJsx::new(Rc::clone(&ast), react_options)); } + + t.regexp_flags = RegexpFlags::new_with_transform_target(Rc::clone(&ast), options.target); + if options.target < TransformTarget::ES2022 { t.es2022_class_static_block.replace(es2022::ClassStaticBlock::new(Rc::clone(&ast))); } @@ -79,7 +84,6 @@ impl<'a> Transformer<'a> { } if options.target < TransformTarget::ES2015 { t.es2015_shorthand_properties.replace(ShorthandProperties::new(Rc::clone(&ast))); - t.es2015_sticky_regex.replace(es2015::StickyRegex::new(Rc::clone(&ast))); } t } @@ -93,9 +97,10 @@ impl<'a> VisitMut<'a> for Transformer<'a> { fn visit_expression(&mut self, expr: &mut Expression<'a>) { // self.typescript.as_mut().map(|t| t.transform_expression(expr)); // self.react_jsx.as_mut().map(|t| t.transform_expression(expr)); + self.regexp_flags.as_mut().map(|t| t.transform_expression(expr)); + self.es2021_logical_assignment_operators.as_mut().map(|t| t.transform_expression(expr)); self.es2016_exponentiation_operator.as_mut().map(|t| t.transform_expression(expr)); - self.es2015_sticky_regex.as_mut().map(|t| t.transform_expression(expr)); self.visit_expression_match(expr); } diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 01aa5ee78a6eb..f9d7e8c9df5e1 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -10,9 +10,11 @@ pub enum TransformTarget { ES5, ES2015, ES2016, + ES2018, ES2019, ES2021, ES2022, + ES2024, #[default] ESNext, } diff --git a/crates/oxc_transformer/src/regexp/mod.rs b/crates/oxc_transformer/src/regexp/mod.rs new file mode 100644 index 0000000000000..8d924a96549b0 --- /dev/null +++ b/crates/oxc_transformer/src/regexp/mod.rs @@ -0,0 +1,3 @@ +mod regexp_flags; + +pub use regexp_flags::RegexpFlags; diff --git a/crates/oxc_transformer/src/regexp/regexp_flags.rs b/crates/oxc_transformer/src/regexp/regexp_flags.rs new file mode 100644 index 0000000000000..83c1e70753062 --- /dev/null +++ b/crates/oxc_transformer/src/regexp/regexp_flags.rs @@ -0,0 +1,71 @@ +use oxc_ast::{ast::*, AstBuilder}; +use oxc_span::{Atom, Span}; + +use std::rc::Rc; + +use crate::TransformTarget; + +/// Transforms unsupported regex flags into Regex constructors. +/// +/// i.e. `/regex/flags` -> `new RegExp('regex', 'flags')` +/// +/// * ES2024 [Unicode Sets v](https://babel.dev/docs/babel-plugin-transform-unicode-sets-regex) +/// * ES2022 [Match Indices d](https://github.com/tc39/proposal-regexp-match-indices) +/// * ES2018 [Dotall s](https://babel.dev/docs/babel-plugin-transform-dotall-regex) +/// * ES2015 [Unicode u](https://babel.dev/docs/babel-plugin-transform-unicode-regex) +/// * ES2015 [Sticky y](https://babel.dev/docs/babel-plugin-transform-sticky-regex) +pub struct RegexpFlags<'a> { + ast: Rc>, + transform_flags: RegExpFlags, +} + +impl<'a> RegexpFlags<'a> { + pub fn new_with_transform_target( + ast: Rc>, + transform_target: TransformTarget, + ) -> Option { + let transform_flags = Self::from_transform_target(transform_target); + (!transform_flags.is_empty()).then(|| Self { ast, transform_flags }) + } + + // `/regex/flags` -> `new RegExp('regex', 'flags')` + pub fn transform_expression(&self, expr: &mut Expression<'a>) { + let Expression::RegExpLiteral(literal) = expr else { return }; + let regex = &literal.regex; + if regex.flags.intersection(self.transform_flags).is_empty() { + return; + } + let ident = IdentifierReference::new(Span::default(), Atom::from("RegExp")); + let callee = self.ast.identifier_expression(ident); + let pattern = self.ast.string_literal(Span::default(), Atom::from(regex.pattern.as_str())); + let flags = self.ast.string_literal(Span::default(), Atom::from(regex.flags.to_string())); + let pattern_literal = self.ast.literal_string_expression(pattern); + let flags_literal = self.ast.literal_string_expression(flags); + let mut arguments = self.ast.new_vec_with_capacity(2); + arguments.push(Argument::Expression(pattern_literal)); + arguments.push(Argument::Expression(flags_literal)); + *expr = self.ast.new_expression(Span::default(), callee, arguments, None); + } + + fn from_transform_target(value: TransformTarget) -> RegExpFlags { + let mut flag = RegExpFlags::empty(); + if value < TransformTarget::ES2015 { + flag |= RegExpFlags::Y; + flag |= RegExpFlags::U; + } + if value < TransformTarget::ES2018 { + flag |= RegExpFlags::S; + } + if value < TransformTarget::ES2022 { + flag |= RegExpFlags::D; + } + if value < TransformTarget::ES2024 { + flag |= RegExpFlags::V; + } + if value < TransformTarget::ESNext { + flag |= RegExpFlags::I; + flag |= RegExpFlags::M; + } + flag + } +} diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index ce152cf1576f2..cae9132ed83ff 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,10 @@ -Passed: 97/1080 +Passed: 97/1091 + +# babel-plugin-transform-unicode-sets-regex +* Failed: basic/basic/input.js +* Failed: basic/string-properties/input.js +* Failed: transform-u/basic/input.js +* Failed: transform-u/string-properties/input.js # babel-plugin-transform-class-properties * Failed: assumption-constantSuper/complex-super-class/input.js @@ -701,6 +707,11 @@ Passed: 97/1080 * Failed: regression/gh-7388/input.js * Failed: regression/gh-8323/input.js +# babel-plugin-transform-dotall-regex +* Failed: dotall-regex/simple/input.js +* Failed: dotall-regex/with-unicode-flag/input.js +* Failed: dotall-regex/with-unicode-property-escape/input.js + # babel-plugin-transform-async-to-generator * Failed: assumption-ignoreFunctionLength-true/basic/input.mjs * Failed: assumption-ignoreFunctionLength-true/export-default-function/input.mjs @@ -764,6 +775,12 @@ Passed: 97/1080 * Passed: sticky-regex/basic/input.js * Passed: sticky-regex/ignore-non-sticky/input.js +# babel-plugin-transform-unicode-regex +* Failed: unicode-regex/basic/input.js +* Failed: unicode-regex/ignore-non-unicode/input.js +* Failed: unicode-regex/negated-set/input.js +* Failed: unicode-regex/slash/input.js + # babel-plugin-transform-typescript * Failed: class/abstract-class-decorated/input.ts * Failed: class/abstract-class-decorated-method/input.ts diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index f13a47f1bd0f8..438aaea0fd9cc 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -22,7 +22,7 @@ pub fn babel(options: &BabelOptions) { let cases = [ // ES2024 - // [Regex] "babel-plugin-transform-unicode-sets-regex", + "babel-plugin-transform-unicode-sets-regex", // ES2022 "babel-plugin-transform-class-properties", "babel-plugin-transform-class-static-block", @@ -48,7 +48,7 @@ pub fn babel(options: &BabelOptions) { "babel-plugin-transform-async-generator-functions", "babel-plugin-transform-object-rest-spread", // [Regex] "babel-plugin-transform-unicode-property-regex", - // [Regex] "babel-plugin-transform-dotall-regex", + "babel-plugin-transform-dotall-regex", // [Regex] "babel-plugin-transform-named-capturing-groups-regex", // ES2017 "babel-plugin-transform-async-to-generator", @@ -57,6 +57,7 @@ pub fn babel(options: &BabelOptions) { // ES2015 "babel-plugin-transform-shorthand-properties", "babel-plugin-transform-sticky-regex", + "babel-plugin-transform-unicode-regex", // TypeScript "babel-plugin-transform-typescript", // React