mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
feat(transformer): RegexpFlags (#977)
Co-authored-by: Boshen <boshenc@gmail.com>
This commit is contained in:
parent
eaa0c58e24
commit
dc08c949a2
8 changed files with 105 additions and 51 deletions
|
|
@ -1,5 +1,3 @@
|
|||
mod shorthand_properties;
|
||||
mod sticky_regex;
|
||||
|
||||
pub use shorthand_properties::ShorthandProperties;
|
||||
pub use sticky_regex::StickyRegex;
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_span::{Atom, Span};
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
/// ES2015: Sticky Regex
|
||||
///
|
||||
/// References:
|
||||
/// * <https://babel.dev/docs/babel-plugin-transform-sticky-regex>
|
||||
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-sticky-regex>
|
||||
pub struct StickyRegex<'a> {
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> StickyRegex<'a> {
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>) -> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<TypeScript<'a>>,
|
||||
regexp_flags: Option<RegexpFlags<'a>>,
|
||||
react_jsx: Option<ReactJsx<'a>>,
|
||||
// es2022
|
||||
es2022_class_static_block: Option<es2022::ClassStaticBlock<'a>>,
|
||||
|
|
@ -46,7 +49,6 @@ pub struct Transformer<'a> {
|
|||
es2016_exponentiation_operator: Option<ExponentiationOperator<'a>>,
|
||||
// es2015
|
||||
es2015_shorthand_properties: Option<ShorthandProperties<'a>>,
|
||||
es2015_sticky_regex: Option<es2015::StickyRegex<'a>>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ pub enum TransformTarget {
|
|||
ES5,
|
||||
ES2015,
|
||||
ES2016,
|
||||
ES2018,
|
||||
ES2019,
|
||||
ES2021,
|
||||
ES2022,
|
||||
ES2024,
|
||||
#[default]
|
||||
ESNext,
|
||||
}
|
||||
|
|
|
|||
3
crates/oxc_transformer/src/regexp/mod.rs
Normal file
3
crates/oxc_transformer/src/regexp/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod regexp_flags;
|
||||
|
||||
pub use regexp_flags::RegexpFlags;
|
||||
71
crates/oxc_transformer/src/regexp/regexp_flags.rs
Normal file
71
crates/oxc_transformer/src/regexp/regexp_flags.rs
Normal file
|
|
@ -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<AstBuilder<'a>>,
|
||||
transform_flags: RegExpFlags,
|
||||
}
|
||||
|
||||
impl<'a> RegexpFlags<'a> {
|
||||
pub fn new_with_transform_target(
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
transform_target: TransformTarget,
|
||||
) -> Option<Self> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue