From b05bc7eec06a665d99abe025883a313832b29125 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 10 Oct 2023 23:05:45 +0800 Subject: [PATCH 1/2] feat(transformer): RegexpFlags --- crates/oxc_transformer/src/es2015/mod.rs | 2 - crates/oxc_transformer/src/lib.rs | 11 +++- crates/oxc_transformer/src/options.rs | 2 + crates/oxc_transformer/src/regexp/mod.rs | 3 + .../regexp_flags.rs} | 57 +++++++++++++++---- 5 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 crates/oxc_transformer/src/regexp/mod.rs rename crates/oxc_transformer/src/{es2015/sticky_regex.rs => regexp/regexp_flags.rs} (50%) 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/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/es2015/sticky_regex.rs b/crates/oxc_transformer/src/regexp/regexp_flags.rs similarity index 50% rename from crates/oxc_transformer/src/es2015/sticky_regex.rs rename to crates/oxc_transformer/src/regexp/regexp_flags.rs index 294713d1a7d72..033ad3448c664 100644 --- a/crates/oxc_transformer/src/es2015/sticky_regex.rs +++ b/crates/oxc_transformer/src/regexp/regexp_flags.rs @@ -3,23 +3,32 @@ use oxc_span::{Atom, Span}; use std::rc::Rc; -/// ES2015: Sticky Regex -/// -/// References: -/// * -/// * -pub struct StickyRegex<'a> { +use crate::TransformTarget; + +/// Regexp +pub struct RegexpFlags<'a> { ast: Rc>, + transform_flags: RegExpFlags, } -impl<'a> StickyRegex<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } +impl<'a> RegexpFlags<'a> { + pub fn new_with_transform_target( + ast: Rc>, + transform_target: TransformTarget, + ) -> Option { + let transform_flags = from_transform_target(transform_target); + + if transform_flags.is_empty() { + None + } else { + Some(Self { ast, transform_flags }) + } } 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) { + + if reg_literal.regex.flags.intersection(self.transform_flags).is_empty() { return; } @@ -41,3 +50,31 @@ impl<'a> StickyRegex<'a> { *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::ESNext { + flag |= RegExpFlags::I; + flag |= RegExpFlags::M; + } + + if value < TransformTarget::ES2024 { + flag |= RegExpFlags::V; + } + + if value < TransformTarget::ES2022 { + flag |= RegExpFlags::D; + } + + if value < TransformTarget::ES2018 { + flag |= RegExpFlags::S; + } + + if value < TransformTarget::ES2015 { + flag |= RegExpFlags::Y; + flag |= RegExpFlags::U; + } + + flag +} From 4fcd1292b361ba7ff7c87844c267110cf17f9551 Mon Sep 17 00:00:00 2001 From: Boshen Date: Wed, 11 Oct 2023 11:23:51 +0800 Subject: [PATCH 2/2] add docs, tests and shorten code --- .../src/regexp/regexp_flags.rs | 89 +++++++++---------- tasks/transform_conformance/babel.snap.md | 19 +++- tasks/transform_conformance/src/lib.rs | 5 +- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/crates/oxc_transformer/src/regexp/regexp_flags.rs b/crates/oxc_transformer/src/regexp/regexp_flags.rs index 033ad3448c664..83c1e70753062 100644 --- a/crates/oxc_transformer/src/regexp/regexp_flags.rs +++ b/crates/oxc_transformer/src/regexp/regexp_flags.rs @@ -5,7 +5,15 @@ use std::rc::Rc; use crate::TransformTarget; -/// Regexp +/// 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, @@ -16,65 +24,48 @@ impl<'a> RegexpFlags<'a> { ast: Rc>, transform_target: TransformTarget, ) -> Option { - let transform_flags = from_transform_target(transform_target); - - if transform_flags.is_empty() { - None - } else { - Some(Self { ast, transform_flags }) - } + let transform_flags = Self::from_transform_target(transform_target); + (!transform_flags.is_empty()).then(|| Self { ast, transform_flags }) } - 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.intersection(self.transform_flags).is_empty() { + // `/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_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 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::ESNext { - flag |= RegExpFlags::I; - flag |= RegExpFlags::M; - } - - if value < TransformTarget::ES2024 { - flag |= RegExpFlags::V; - } - - if value < TransformTarget::ES2022 { - flag |= RegExpFlags::D; - } - if value < TransformTarget::ES2018 { - flag |= RegExpFlags::S; - } - - if value < TransformTarget::ES2015 { - flag |= RegExpFlags::Y; - flag |= RegExpFlags::U; + 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 } - - 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