feat(es/compat): Support more regex expressions (#4205)

This commit is contained in:
magic-akari 2022-04-01 12:26:30 +08:00 committed by GitHub
parent a05a0d58a6
commit b793aa0200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 240 additions and 14 deletions

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2017"
}
}

View File

@ -0,0 +1,3 @@
const v0 = "Hello\nWorld";
/Hello.World/.test(v0);
/Hello.World/s.test(v0);

View File

@ -0,0 +1,3 @@
const v0 = "Hello\nWorld";
/Hello.World/.test(v0);
RegExp("Hello.World", "s").test(v0);

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2017"
}
}

View File

@ -0,0 +1,2 @@
var a = /./;
var b = /./s;

View File

@ -0,0 +1,2 @@
var a = /./;
var b = RegExp(".", "s");

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2017"
}
}

View File

@ -0,0 +1,2 @@
var a = /./u;
var b = /./su;

View File

@ -0,0 +1,2 @@
var a = /./u;
var b = RegExp(".", "su");

View File

@ -0,0 +1,5 @@
{
"jsc": {
"target": "es2017"
}
}

View File

@ -0,0 +1,2 @@
var a = /\p{Unified_Ideograph}./u;
var b = /\p{Unified_Ideograph}./su;

View File

@ -0,0 +1,2 @@
var a = RegExp("\\p{Unified_Ideograph}.", "u");
var b = RegExp("\\p{Unified_Ideograph}.", "su");

View File

@ -1,6 +1,6 @@
// @target: es5 // @target: es5
// @lib: es6,es2018 // @lib: es6,es2018
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; let re = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u");
let result = re.exec("2015-01-02"); let result = re.exec("2015-01-02");
let date = result[0]; let date = result[0];
let year1 = result.groups.year; let year1 = result.groups.year;
@ -9,4 +9,4 @@ let month1 = result.groups.month;
let month2 = result[2]; let month2 = result[2];
let day1 = result.groups.day; let day1 = result.groups.day;
let day2 = result[3]; let day2 = result[3];
let foo = "foo".match(/(?<bar>foo)/).groups.foo; let foo = "foo".match(RegExp("(?<bar>foo)")).groups.foo;

View File

@ -1,2 +1,2 @@
let result = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u.exec("2015-01-02"); let result = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u").exec("2015-01-02");
result[0], result.groups.year, result[1], result.groups.month, result[2], result.groups.day, result[3], "foo".match(/(?<bar>foo)/).groups.foo; result[0], result.groups.year, result[1], result.groups.month, result[2], result.groups.day, result[3], "foo".match(RegExp("(?<bar>foo)")).groups.foo;

View File

@ -1,6 +1,6 @@
// @target: es5 // @target: es5
// @lib: es6,es2018 // @lib: es6,es2018
var re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; var re = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u");
var result = re.exec("2015-01-02"); var result = re.exec("2015-01-02");
var date = result[0]; var date = result[0];
var year1 = result.groups.year; var year1 = result.groups.year;
@ -9,4 +9,4 @@ var month1 = result.groups.month;
var month2 = result[2]; var month2 = result[2];
var day1 = result.groups.day; var day1 = result.groups.day;
var day2 = result[3]; var day2 = result[3];
var foo = "foo".match(/(?<bar>foo)/).groups.foo; var foo = "foo".match(RegExp("(?<bar>foo)")).groups.foo;

View File

@ -1,2 +1,2 @@
var result = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u.exec("2015-01-02"); var result = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u").exec("2015-01-02");
result[0], result.groups.year, result[1], result.groups.month, result[2], result.groups.day, result[3], "foo".match(/(?<bar>foo)/).groups.foo; result[0], result.groups.year, result[1], result.groups.month, result[2], result.groups.day, result[3], "foo".match(RegExp("(?<bar>foo)")).groups.foo;

View File

@ -888,6 +888,15 @@ impl Spanned for ExprOrSpread {
} }
} }
impl From<Expr> for ExprOrSpread {
fn from(e: Expr) -> Self {
Self {
spread: None,
expr: Box::new(e),
}
}
}
#[ast_node] #[ast_node]
#[derive(Eq, Hash, Is, EqIgnoreSpan)] #[derive(Eq, Hash, Is, EqIgnoreSpan)]
#[allow(variant_size_differences)] #[allow(variant_size_differences)]

View File

@ -233,6 +233,16 @@ pub struct Regex {
pub flags: JsWord, pub flags: JsWord,
} }
impl Take for Regex {
fn dummy() -> Self {
Self {
span: DUMMY_SP,
exp: Default::default(),
flags: Default::default(),
}
}
}
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))] #[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a> arbitrary::Arbitrary<'a> for Regex { impl<'a> arbitrary::Arbitrary<'a> for Regex {

View File

@ -138,6 +138,8 @@ static URL_SEARCH_PARAMS_DEPENDENCIES: &[&str] = &[
"web.dom-collections.iterator", "web.dom-collections.iterator",
]; ];
pub static REGEXP_DEPENDENCIES: &[&str] = &["es.regexp.constructor"];
pub static BUILTINS: DataMap<&[&str]> = data_map!(Map { pub static BUILTINS: DataMap<&[&str]> = data_map!(Map {
AggregateError: [ AggregateError: [
"esnext.aggregate-error", "esnext.aggregate-error",
@ -477,6 +479,7 @@ pub static INSTANCE_PROPERTIES: DataMap<&[&str]> = data_map!(Map {
concat: ["es.array.concat"], concat: ["es.array.concat"],
copyWithin: ["es.array.copy-within"], copyWithin: ["es.array.copy-within"],
description: ["es.symbol", "es.symbol.description"], description: ["es.symbol", "es.symbol.description"],
dotAll: [ "es.regexp.dot-all"],
endsWith: ["es.string.ends-with"], endsWith: ["es.string.ends-with"],
entries: ARRAY_NATURE_ITERATORS_WITH_TAG, entries: ARRAY_NATURE_ITERATORS_WITH_TAG,
every: ["es.array.every"], every: ["es.array.every"],
@ -522,6 +525,7 @@ pub static INSTANCE_PROPERTIES: DataMap<&[&str]> = data_map!(Map {
splice: ["es.array.splice"], splice: ["es.array.splice"],
split: ["es.string.split", "es.regexp.exec"], split: ["es.string.split", "es.regexp.exec"],
startsWith: ["es.string.starts-with"], startsWith: ["es.string.starts-with"],
sticky:["es.regexp.sticky"],
strike: ["es.string.strike"], strike: ["es.string.strike"],
sub: ["es.string.sub"], sub: ["es.string.sub"],
sup: ["es.string.sup"], sup: ["es.string.sup"],

View File

@ -11,7 +11,7 @@ use crate::{
compat::DATA as CORE_JS_COMPAT_DATA, compat::DATA as CORE_JS_COMPAT_DATA,
data::{ data::{
COMMON_ITERATORS, INSTANCE_PROPERTIES, POSSIBLE_GLOBAL_OBJECTS, PROMISE_DEPENDENCIES, COMMON_ITERATORS, INSTANCE_PROPERTIES, POSSIBLE_GLOBAL_OBJECTS, PROMISE_DEPENDENCIES,
STATIC_PROPERTIES, REGEXP_DEPENDENCIES, STATIC_PROPERTIES,
}, },
}, },
util::DataMapExt, util::DataMapExt,
@ -173,12 +173,20 @@ impl Visit for UsageVisitor {
} }
/// import('something').then(...) /// import('something').then(...)
/// RegExp(".", "us")
fn visit_call_expr(&mut self, e: &CallExpr) { fn visit_call_expr(&mut self, e: &CallExpr) {
e.visit_children_with(self); e.visit_children_with(self);
if let Callee::Import(_) = &e.callee { match &e.callee {
self.add(PROMISE_DEPENDENCIES) Callee::Import(_) => self.add(PROMISE_DEPENDENCIES),
} Callee::Expr(expr) => match **expr {
Expr::Ident(ref ident) if ident.sym == js_word!("RegExp") => {
self.add(REGEXP_DEPENDENCIES)
}
_ => {}
},
_ => {}
};
} }
fn visit_expr(&mut self, e: &Expr) { fn visit_expr(&mut self, e: &Expr) {

View File

@ -11,7 +11,10 @@ use swc_atoms::{js_word, JsWord};
use swc_common::{chain, collections::AHashSet, comments::Comments, FromVariant, Mark, DUMMY_SP}; use swc_common::{chain, collections::AHashSet, comments::Comments, FromVariant, Mark, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_transforms::{ use swc_ecma_transforms::{
compat::{bugfixes, es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es3}, compat::{
bugfixes, es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es3,
regexp::{self, regexp},
},
pass::{noop, Optional}, pass::{noop, Optional},
Assumptions, Assumptions,
}; };
@ -89,6 +92,37 @@ where
bugfixes::safari_id_destructuring_collision_in_function_expression() bugfixes::safari_id_destructuring_collision_in_function_expression()
); );
let pass = {
let enable_dot_all_regex = should_enable!(DotAllRegex, false);
let enable_named_capturing_groups_regex = should_enable!(NamedCapturingGroupsRegex, false);
let enable_sticky_regex = should_enable!(StickyRegex, false);
let enable_unicode_property_regex = should_enable!(UnicodePropertyRegex, false);
let enable_unicode_regex = should_enable!(UnicodeRegex, false);
let enable = enable_dot_all_regex
|| enable_named_capturing_groups_regex
|| enable_sticky_regex
|| enable_unicode_property_regex
|| enable_unicode_regex;
chain!(
pass,
Optional::new(
regexp(regexp::Config {
dot_all_regex: enable_dot_all_regex,
// TODO: add Feature::LookbehindAssertion
lookbehind_assertion: false,
named_capturing_groups_regex: enable_named_capturing_groups_regex,
sticky_regex: enable_sticky_regex,
unicode_property_regex: enable_unicode_property_regex,
unicode_regex: enable_unicode_regex,
}),
enable
)
)
};
// Proposals // Proposals
// ES2022 // ES2022

View File

@ -0,0 +1,8 @@
const a = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
const b = /./s;
const c = /./imsuy;
console.log(a.unicode);
console.log(b.dotAll);
console.log(c.sticky);
console.log(c.ignoreCase);

View File

@ -0,0 +1,15 @@
{
"presets": [
[
"../../../../lib",
{
"targets": {
"browsers": ["ie > 10"]
},
"useBuiltIns": "usage",
"corejs": 3,
"modules": false
}
]
]
}

View File

@ -0,0 +1,12 @@
import "core-js/modules/es.regexp.constructor.js";
import "core-js/modules/es.regexp.dot-all.js";
import "core-js/modules/es.regexp.exec.js";
import "core-js/modules/es.regexp.sticky.js";
import "core-js/modules/es.regexp.to-string.js";
var a = RegExp("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})", "u");
var b = RegExp(".", "s");
var c = RegExp(".", "imsuy");
console.log(a.unicode);
console.log(b.dotAll);
console.log(c.sticky);
console.log(c.ignoreCase);

View File

@ -11,6 +11,7 @@ pub use self::{
spread::spread, sticky_regex::sticky_regex, template_literal::template_literal, spread::spread, sticky_regex::sticky_regex, template_literal::template_literal,
typeof_symbol::typeof_symbol, typeof_symbol::typeof_symbol,
}; };
use crate::regexp::{self, regexp};
mod arrow; mod arrow;
mod block_scoped_fn; mod block_scoped_fn;
@ -50,6 +51,14 @@ where
C: Comments, C: Comments,
{ {
chain!( chain!(
regexp(regexp::Config {
dot_all_regex: false,
lookbehind_assertion: false,
named_capturing_groups_regex: false,
sticky_regex: true,
unicode_property_regex: false,
unicode_regex: true,
}),
block_scoped_functions(), block_scoped_functions(),
template_literal(c.template_literal), template_literal(c.template_literal),
classes(comments, c.classes), classes(comments, c.classes),

View File

@ -1,13 +1,25 @@
use serde::Deserialize; use serde::Deserialize;
use swc_common::chain;
use swc_ecma_visit::Fold; use swc_ecma_visit::Fold;
pub use self::object_rest_spread::object_rest_spread; pub use self::object_rest_spread::object_rest_spread;
use crate::regexp::{self, regexp};
pub mod object_rest_spread; pub mod object_rest_spread;
#[tracing::instrument(level = "info", skip_all)] #[tracing::instrument(level = "info", skip_all)]
pub fn es2018(c: Config) -> impl Fold { pub fn es2018(c: Config) -> impl Fold {
object_rest_spread(c.object_rest_spread) chain!(
regexp(regexp::Config {
dot_all_regex: true,
lookbehind_assertion: true,
named_capturing_groups_regex: true,
sticky_regex: false,
unicode_property_regex: true,
unicode_regex: false,
}),
object_rest_spread(c.object_rest_spread)
)
} }
#[derive(Debug, Clone, Copy, Default, Deserialize)] #[derive(Debug, Clone, Copy, Default, Deserialize)]

View File

@ -19,4 +19,5 @@ pub mod es2020;
pub mod es2021; pub mod es2021;
pub mod es2022; pub mod es2022;
pub mod es3; pub mod es3;
pub mod regexp;
pub mod reserved_words; pub mod reserved_words;

View File

@ -0,0 +1,66 @@
use swc_common::util::take::Take;
use swc_ecma_ast::{CallExpr, Expr, Lit, Regex};
use swc_ecma_utils::{quote_ident, ExprFactory};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
pub fn regexp(config: Config) -> impl Fold + VisitMut {
as_folder(RegExp { config })
}
#[derive(Default, Clone, Copy)]
pub struct Config {
/// [s/dotAll flag for regular expressions](https://tc39.github.io/proposal-regexp-dotall-flag/)
pub dot_all_regex: bool,
/// [RegExp Lookbehind Assertions](https://tc39.es/proposal-regexp-lookbehind/)
pub lookbehind_assertion: bool,
/// [Named capture groups in regular expressions](https://tc39.es/proposal-regexp-named-groups/)
pub named_capturing_groups_regex: bool,
/// [RegExp.prototype.sticky](https://tc39.es/ecma262/multipage/text-processing.html#sec-get-regexp.prototype.sticky)
pub sticky_regex: bool,
/// [Unicode property escapes in regular expressions](https://tc39.es/proposal-regexp-unicode-property-escapes/)
pub unicode_property_regex: bool,
/// [RegExp.prototype.unicode](https://tc39.es/ecma262/multipage/text-processing.html#sec-get-regexp.prototype.unicode)
pub unicode_regex: bool,
}
struct RegExp {
config: Config,
}
impl VisitMut for RegExp {
noop_visit_mut_type!();
fn visit_mut_expr(&mut self, expr: &mut Expr) {
expr.visit_mut_children_with(self);
if let Expr::Lit(Lit::Regex(regex)) = expr {
if (self.config.dot_all_regex && regex.flags.contains('s'))
|| (self.config.sticky_regex && regex.flags.contains('y'))
|| (self.config.unicode_regex && regex.flags.contains('u'))
|| (self.config.named_capturing_groups_regex && regex.exp.contains("(?<"))
|| (self.config.lookbehind_assertion && regex.exp.contains("(?<=")
|| regex.exp.contains("(?<!"))
|| (self.config.unicode_property_regex
&& (regex.exp.contains("\\p{") || regex.exp.contains("\\P{")))
{
let Regex { exp, flags, span } = regex.take();
let exp: Expr = exp.into();
let mut args = vec![exp.into()];
if !flags.is_empty() {
let flags: Expr = flags.into();
args.push(flags.into());
}
*expr = CallExpr {
span,
callee: quote_ident!("RegExp").as_callee(),
args,
type_args: None,
}
.into()
}
}
}
}