mirror of
https://github.com/swc-project/swc.git
synced 2024-12-03 13:17:09 +03:00
feat(es/compat): Support more regex expressions (#4205)
This commit is contained in:
parent
a05a0d58a6
commit
b793aa0200
5
crates/swc/tests/fixture/regexp/dotall/1/input/.swcrc
Normal file
5
crates/swc/tests/fixture/regexp/dotall/1/input/.swcrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017"
|
||||
}
|
||||
}
|
3
crates/swc/tests/fixture/regexp/dotall/1/input/index.js
Normal file
3
crates/swc/tests/fixture/regexp/dotall/1/input/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
const v0 = "Hello\nWorld";
|
||||
/Hello.World/.test(v0);
|
||||
/Hello.World/s.test(v0);
|
3
crates/swc/tests/fixture/regexp/dotall/1/output/index.js
Normal file
3
crates/swc/tests/fixture/regexp/dotall/1/output/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
const v0 = "Hello\nWorld";
|
||||
/Hello.World/.test(v0);
|
||||
RegExp("Hello.World", "s").test(v0);
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017"
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
var a = /./;
|
||||
var b = /./s;
|
@ -0,0 +1,2 @@
|
||||
var a = /./;
|
||||
var b = RegExp(".", "s");
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017"
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
var a = /./u;
|
||||
var b = /./su;
|
@ -0,0 +1,2 @@
|
||||
var a = /./u;
|
||||
var b = RegExp(".", "su");
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017"
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
var a = /\p{Unified_Ideograph}./u;
|
||||
var b = /\p{Unified_Ideograph}./su;
|
@ -0,0 +1,2 @@
|
||||
var a = RegExp("\\p{Unified_Ideograph}.", "u");
|
||||
var b = RegExp("\\p{Unified_Ideograph}.", "su");
|
@ -1,6 +1,6 @@
|
||||
// @target: es5
|
||||
// @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 date = result[0];
|
||||
let year1 = result.groups.year;
|
||||
@ -9,4 +9,4 @@ let month1 = result.groups.month;
|
||||
let month2 = result[2];
|
||||
let day1 = result.groups.day;
|
||||
let day2 = result[3];
|
||||
let foo = "foo".match(/(?<bar>foo)/).groups.foo;
|
||||
let foo = "foo".match(RegExp("(?<bar>foo)")).groups.foo;
|
||||
|
@ -1,2 +1,2 @@
|
||||
let result = /(?<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;
|
||||
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(RegExp("(?<bar>foo)")).groups.foo;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// @target: es5
|
||||
// @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 date = result[0];
|
||||
var year1 = result.groups.year;
|
||||
@ -9,4 +9,4 @@ var month1 = result.groups.month;
|
||||
var month2 = result[2];
|
||||
var day1 = result.groups.day;
|
||||
var day2 = result[3];
|
||||
var foo = "foo".match(/(?<bar>foo)/).groups.foo;
|
||||
var foo = "foo".match(RegExp("(?<bar>foo)")).groups.foo;
|
||||
|
@ -1,2 +1,2 @@
|
||||
var result = /(?<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;
|
||||
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(RegExp("(?<bar>foo)")).groups.foo;
|
||||
|
@ -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]
|
||||
#[derive(Eq, Hash, Is, EqIgnoreSpan)]
|
||||
#[allow(variant_size_differences)]
|
||||
|
@ -233,6 +233,16 @@ pub struct Regex {
|
||||
pub flags: JsWord,
|
||||
}
|
||||
|
||||
impl Take for Regex {
|
||||
fn dummy() -> Self {
|
||||
Self {
|
||||
span: DUMMY_SP,
|
||||
exp: Default::default(),
|
||||
flags: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "arbitrary")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for Regex {
|
||||
|
@ -138,6 +138,8 @@ static URL_SEARCH_PARAMS_DEPENDENCIES: &[&str] = &[
|
||||
"web.dom-collections.iterator",
|
||||
];
|
||||
|
||||
pub static REGEXP_DEPENDENCIES: &[&str] = &["es.regexp.constructor"];
|
||||
|
||||
pub static BUILTINS: DataMap<&[&str]> = data_map!(Map {
|
||||
AggregateError: [
|
||||
"esnext.aggregate-error",
|
||||
@ -477,6 +479,7 @@ pub static INSTANCE_PROPERTIES: DataMap<&[&str]> = data_map!(Map {
|
||||
concat: ["es.array.concat"],
|
||||
copyWithin: ["es.array.copy-within"],
|
||||
description: ["es.symbol", "es.symbol.description"],
|
||||
dotAll: [ "es.regexp.dot-all"],
|
||||
endsWith: ["es.string.ends-with"],
|
||||
entries: ARRAY_NATURE_ITERATORS_WITH_TAG,
|
||||
every: ["es.array.every"],
|
||||
@ -522,6 +525,7 @@ pub static INSTANCE_PROPERTIES: DataMap<&[&str]> = data_map!(Map {
|
||||
splice: ["es.array.splice"],
|
||||
split: ["es.string.split", "es.regexp.exec"],
|
||||
startsWith: ["es.string.starts-with"],
|
||||
sticky:["es.regexp.sticky"],
|
||||
strike: ["es.string.strike"],
|
||||
sub: ["es.string.sub"],
|
||||
sup: ["es.string.sup"],
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
compat::DATA as CORE_JS_COMPAT_DATA,
|
||||
data::{
|
||||
COMMON_ITERATORS, INSTANCE_PROPERTIES, POSSIBLE_GLOBAL_OBJECTS, PROMISE_DEPENDENCIES,
|
||||
STATIC_PROPERTIES,
|
||||
REGEXP_DEPENDENCIES, STATIC_PROPERTIES,
|
||||
},
|
||||
},
|
||||
util::DataMapExt,
|
||||
@ -173,12 +173,20 @@ impl Visit for UsageVisitor {
|
||||
}
|
||||
|
||||
/// import('something').then(...)
|
||||
/// RegExp(".", "us")
|
||||
fn visit_call_expr(&mut self, e: &CallExpr) {
|
||||
e.visit_children_with(self);
|
||||
|
||||
if let Callee::Import(_) = &e.callee {
|
||||
self.add(PROMISE_DEPENDENCIES)
|
||||
}
|
||||
match &e.callee {
|
||||
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) {
|
||||
|
@ -11,7 +11,10 @@ use swc_atoms::{js_word, JsWord};
|
||||
use swc_common::{chain, collections::AHashSet, comments::Comments, FromVariant, Mark, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
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},
|
||||
Assumptions,
|
||||
};
|
||||
@ -89,6 +92,37 @@ where
|
||||
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
|
||||
|
||||
// ES2022
|
||||
|
8
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/input.mjs
vendored
Normal file
8
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/input.mjs
vendored
Normal 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);
|
15
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/options.json
vendored
Normal file
15
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/options.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"../../../../lib",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": ["ie > 10"]
|
||||
},
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": 3,
|
||||
"modules": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
12
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs
vendored
Normal file
12
crates/swc_ecma_preset_env/tests/fixtures/corejs3/usage-regexp/output.mjs
vendored
Normal 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);
|
@ -11,6 +11,7 @@ pub use self::{
|
||||
spread::spread, sticky_regex::sticky_regex, template_literal::template_literal,
|
||||
typeof_symbol::typeof_symbol,
|
||||
};
|
||||
use crate::regexp::{self, regexp};
|
||||
|
||||
mod arrow;
|
||||
mod block_scoped_fn;
|
||||
@ -50,6 +51,14 @@ where
|
||||
C: Comments,
|
||||
{
|
||||
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(),
|
||||
template_literal(c.template_literal),
|
||||
classes(comments, c.classes),
|
||||
|
@ -1,13 +1,25 @@
|
||||
use serde::Deserialize;
|
||||
use swc_common::chain;
|
||||
use swc_ecma_visit::Fold;
|
||||
|
||||
pub use self::object_rest_spread::object_rest_spread;
|
||||
use crate::regexp::{self, regexp};
|
||||
|
||||
pub mod object_rest_spread;
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
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)]
|
||||
|
@ -19,4 +19,5 @@ pub mod es2020;
|
||||
pub mod es2021;
|
||||
pub mod es2022;
|
||||
pub mod es3;
|
||||
pub mod regexp;
|
||||
pub mod reserved_words;
|
||||
|
66
crates/swc_ecma_transforms_compat/src/regexp.rs
Normal file
66
crates/swc_ecma_transforms_compat/src/regexp.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user