fix(es/transforms/compat): Implement new.tartet (#2129)

swc_ecma_transforms_compat:
 - Handle `new.target`. (#1179)
This commit is contained in:
강동윤 2021-08-22 15:56:49 +09:00 committed by GitHub
parent 61e58d7329
commit c78baef2cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 683 additions and 9 deletions

4
Cargo.lock generated
View File

@ -2491,7 +2491,7 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.67.0"
version = "0.67.1"
dependencies = [
"either",
"enum_kind",
@ -2599,7 +2599,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_compat"
version = "0.30.0"
version = "0.30.1"
dependencies = [
"arrayvec",
"fxhash",

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "examples/**/*.rs"]
license = "Apache-2.0/MIT"
name = "swc_ecma_parser"
repository = "https://github.com/swc-project/swc.git"
version = "0.67.0"
version = "0.67.1"
[package.metadata.docs.rs]
all-features = true

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms_compat"
repository = "https://github.com/swc-project/swc.git"
version = "0.30.0"
version = "0.30.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -20,6 +20,7 @@ mod duplicate_keys;
pub mod for_of;
mod function_name;
mod instanceof;
pub mod new_target;
mod parameters;
mod regenerator;
mod shorthand_property;

View File

@ -0,0 +1,257 @@
use std::borrow::Cow;
use swc_atoms::js_word;
use swc_common::{pass::CompilerPass, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::{prepend, private_ident, quote_ident, undefined, ExprFactory};
use swc_ecma_visit::{
as_folder, noop_visit_mut_type, noop_visit_type, Fold, Node, Visit, VisitMut, VisitMutWith,
};
pub fn new_target() -> impl Fold + VisitMut + CompilerPass {
as_folder(NewTarget::default())
}
#[derive(Default)]
struct NewTarget {
cur: Option<Ident>,
in_constructor: bool,
in_method: bool,
in_arrow_expr: bool,
var: Option<VarDeclarator>,
}
#[fast_path(ShouldWork)]
impl VisitMut for NewTarget {
noop_visit_mut_type!();
fn visit_mut_arrow_expr(&mut self, e: &mut ArrowExpr) {
// #[fast_path] ensures that `e` contains new.target
let old = self.in_arrow_expr;
if self.var.is_none() {
let mut v = Expr::MetaProp(MetaPropExpr {
meta: Ident::new("new".into(), DUMMY_SP),
prop: Ident::new("target".into(), DUMMY_SP),
});
v.visit_mut_with(self);
self.var.get_or_insert_with(|| VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(private_ident!("_newtarget").into()),
init: Some(Box::new(v)),
definite: Default::default(),
});
}
self.in_arrow_expr = true;
e.visit_mut_children_with(self);
self.in_arrow_expr = old;
}
fn visit_mut_class_decl(&mut self, class: &mut ClassDecl) {
let old = self.cur.take();
self.cur = Some(class.ident.clone());
class.visit_mut_children_with(self);
self.cur = old;
}
fn visit_mut_class_expr(&mut self, class: &mut ClassExpr) {
let old = self.cur.take();
self.cur = class.ident.clone();
class.visit_mut_children_with(self);
self.cur = old;
}
fn visit_mut_class_method(&mut self, c: &mut ClassMethod) {
let old = self.in_method;
self.in_method = true;
c.visit_mut_children_with(self);
self.in_method = old;
}
fn visit_mut_constructor(&mut self, c: &mut Constructor) {
let old = self.in_constructor;
self.in_constructor = true;
c.visit_mut_children_with(self);
self.in_constructor = old;
}
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
match e {
Expr::MetaProp(MetaPropExpr {
meta:
Ident {
sym: js_word!("new"),
..
},
prop:
Ident {
sym: js_word!("target"),
..
},
}) => {
if self.in_arrow_expr {
*e = Expr::Ident(self.var.as_ref().unwrap().name.clone().ident().unwrap().id);
} else if self.in_method {
*e = *undefined(DUMMY_SP)
} else {
if let Some(cur) = self.cur.clone() {
let c =
ThisExpr { span: DUMMY_SP }.make_member(quote_ident!("constructor"));
if self.in_constructor {
*e = c;
} else {
// (this instanceof Foo ? this.constructor : void 0)
*e = Expr::Cond(CondExpr {
span: DUMMY_SP,
// this instanceof Foo
test: Box::new(Expr::Bin(BinExpr {
span: DUMMY_SP,
op: op!("instanceof"),
left: Box::new(Expr::This(ThisExpr { span: DUMMY_SP })),
right: Box::new(Expr::Ident(cur)),
})),
// this.constructor
cons: Box::new(c),
// void 0
alt: undefined(DUMMY_SP),
});
}
}
}
}
_ => {}
}
}
fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
// #[fast_path] ensures that `f` contains `new.target`.
let old = self.cur.take();
self.cur = Some(f.ident.clone());
f.visit_mut_children_with(self);
self.cur = old;
}
fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
// #[fast_path] ensures that `f` contains `new.target`.
let i = f
.ident
.get_or_insert_with(|| private_ident!("_target"))
.clone();
let old = self.cur.take();
self.cur = Some(i.clone());
f.visit_mut_children_with(self);
self.cur = old;
}
fn visit_mut_method_prop(&mut self, m: &mut MethodProp) {
let old = self.in_method;
self.in_method = true;
m.visit_mut_children_with(self);
self.in_method = old;
}
fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
stmts.visit_mut_children_with(self);
if let Some(var) = self.var.take() {
prepend(
stmts,
ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![var],
}))),
)
}
}
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
stmts.visit_mut_children_with(self);
if !self.in_arrow_expr {
if let Some(var) = self.var.take() {
prepend(
stmts,
Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![var],
})),
)
}
}
}
}
impl CompilerPass for NewTarget {
fn name() -> Cow<'static, str> {
Cow::Borrowed("new-target")
}
}
#[derive(Default)]
struct ShouldWork {
found: bool,
}
impl Visit for ShouldWork {
noop_visit_type!();
fn visit_meta_prop_expr(&mut self, n: &MetaPropExpr, _: &dyn Node) {
match n {
MetaPropExpr {
meta:
Ident {
sym: js_word!("new"),
..
},
prop:
Ident {
sym: js_word!("target"),
..
},
} => {
self.found = true;
}
_ => {}
}
}
}
impl Check for ShouldWork {
fn should_handle(&self) -> bool {
self.found
}
}

View File

@ -0,0 +1,16 @@
use std::{fs::read_to_string, path::PathBuf};
use swc_ecma_transforms_compat::es2015::new_target::new_target;
use swc_ecma_transforms_testing::{exec_tr, test_fixture};
#[testing::fixture("tests/fixture/new-target/**/exec.js")]
fn exec(input: PathBuf) {
let input = read_to_string(&input).unwrap();
exec_tr("new-target", Default::default(), |_| new_target(), &input);
}
#[testing::fixture("tests/fixture/new-target/**/input.js")]
fn fixture(input: PathBuf) {
let dir = input.parent().unwrap();
let output = dir.join("output.js");
test_fixture(Default::default(), &|_| new_target(), &input, &output);
}

View File

@ -0,0 +1,22 @@
"use strict";
const targets = [];
class Foo {
constructor() {
targets.push(new.target);
}
}
class Bar extends Foo {
constructor() {
super();
targets.push(new.target);
}
}
new Foo;
new Bar;
expect(targets[0]).toBe(Foo);
expect(targets[1]).toBe(Bar);
expect(targets[2]).toBe(Bar);

View File

@ -0,0 +1,12 @@
"use strict";
const targets = [];
class Foo {
constructor() {
targets.push(new.target);
}
}
new Foo;
expect(targets[0]).toBe(Foo);

View File

@ -0,0 +1,16 @@
"use strict";
const targets = [];
function Foo() {
targets.push(new.target);
}
function Bar() {
Foo.call(this);
}
new Foo;
new Bar();
expect(targets[0]).toBe(Foo);
expect(targets[1]).toBeUndefined();

View File

@ -0,0 +1,10 @@
"use strict";
const targets = [];
function Foo() {
targets.push(new.target);
}
new Foo;
expect(targets[0]).toBe(Foo);

View File

@ -0,0 +1,12 @@
"use strict";
const targets = [];
function foo() {
targets.push(new.target);
}
foo();
foo.call({});
expect(targets[0]).toBeUndefined();
expect(targets[1]).toBeUndefined();

View File

@ -0,0 +1,27 @@
const targets = [];
class Foo {
constructor() {
targets.push(new.target);
}
}
class Bar extends Foo {
}
class Baz {
}
Reflect.construct(Foo, []);
Reflect.construct(Foo, [], Bar);
Reflect.construct(Bar, []);
Reflect.construct(Bar, [], Baz);
Reflect.construct(Foo, [], Baz);
expect(targets[0]).toBe(Foo);
expect(targets[1]).toBe(Bar);
expect(targets[2]).toBe(Bar);
expect(targets[3]).toBe(Baz);
expect(targets[4]).toBe(Baz);

View File

@ -0,0 +1,40 @@
const targets = [];
function Foo() {
targets.push(new.target);
}
function Bar() {
Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype, {
constructor: {
value: Bar,
writable: true,
configurable: true,
}
});
function Baz() {}
Reflect.construct(Foo, []);
Reflect.construct(Foo, [], Bar);
Reflect.construct(Bar, []);
Reflect.construct(Bar, [], Baz);
Reflect.construct(Foo, [], Baz);
expect(targets[0]).toBe(Foo);
expect(targets[1]).toBe(Bar);
expect(() => {
// Wish we could support this...
// Then again, this is what a transformed class does.
expect(targets[2]).toBeUndefined();
}).toThrow();
expect(targets[3]).toBeUndefined();
expect(() => {
// Wish we could support this...
expect(targets[4]).toBe(Baz);
}).toThrow();

View File

@ -0,0 +1,9 @@
class Foo {
constructor() {
this.Bar = class {
static p = new.target
}
}
}
expect((new Foo).Bar.p).toBeUndefined()

View File

@ -0,0 +1,20 @@
class Foo {
constructor() {
this.Bar = class {
static p = new.target
static p1 = class { constructor() { new.target } } // should not replace
static p2 = new function () { new.target } // should not replace
static p3 = () => { new.target } // should replace
static p4 = function () { new.target } // should not replace
q = new.target // should not replace
}
}
test = function() {
new.target;
};
test2 = () => {
new.target;
}
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
"transform-new-target",
"transform-arrow-functions",
["proposal-class-properties", { "loose": true }]
]
}

View File

@ -0,0 +1,35 @@
class Foo {
constructor() {
var _newtarget = this.constructor,
_class,
_temp;
this.test = function _target() {
this instanceof _target ? this.constructor : void 0;
};
this.test2 = function () {
_newtarget;
};
this.Bar = (_temp = _class = class _target2 {
constructor() {
this.q = this.constructor;
} // should not replace
}, _class.p = void 0, _class.p1 = class _target3 {
constructor() {
this.constructor;
}
}, _class.p2 = new function _target4() {
this instanceof _target4 ? this.constructor : void 0;
}(), _class.p3 = function () {
void 0;
}, _class.p4 = function _target5() {
this instanceof _target5 ? this.constructor : void 0;
}, _temp);
}
}

View File

@ -0,0 +1,9 @@
class Foo {
constructor() {
this.Bar = class {
static p = new.target
}
}
}
expect((new Foo).Bar.p).toBeUndefined()

View File

@ -0,0 +1,20 @@
class Foo {
constructor() {
this.Bar = class {
static p = new.target
static p1 = class { constructor() { new.target } } // should not replace
static p2 = new function () { new.target } // should not replace
static p3 = () => { new.target } // should replace
static p4 = function () { new.target } // should not replace
q = new.target // should not replace
}
}
test = function() {
new.target;
};
test2 = () => {
new.target;
}
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
"transform-new-target",
"transform-arrow-functions",
["proposal-class-properties", { "loose": false }]
]
}

View File

@ -0,0 +1,33 @@
class Foo {
constructor() {
var _newtarget = this.constructor,
_class,
_temp;
babelHelpers.defineProperty(this, "test", function _target() {
this instanceof _target ? this.constructor : void 0;
});
babelHelpers.defineProperty(this, "test2", function () {
_newtarget;
});
this.Bar = (_temp = _class = class _target2 {
constructor() {
babelHelpers.defineProperty(this, "q", this.constructor);
} // should not replace
}, babelHelpers.defineProperty(_class, "p", void 0), babelHelpers.defineProperty(_class, "p1", class _target3 {
constructor() {
this.constructor;
}
}), babelHelpers.defineProperty(_class, "p2", new function _target4() {
this instanceof _target4 ? this.constructor : void 0;
}()), babelHelpers.defineProperty(_class, "p3", function () {
void 0;
}), babelHelpers.defineProperty(_class, "p4", function _target5() {
this instanceof _target5 ? this.constructor : void 0;
}), _temp);
}
}

View File

@ -0,0 +1,13 @@
function Foo() {
const a = () => {
new.target;
};
}
class Bar {
constructor() {
const a = () => {
new.target;
};
}
}

View File

@ -0,0 +1,3 @@
{
"plugins": ["transform-new-target", "transform-arrow-functions"]
}

View File

@ -0,0 +1,14 @@
function Foo() {
var _newtarget = this instanceof Foo ? this.constructor : void 0;
const a = ()=>{
_newtarget;
};
}
class Bar {
constructor(){
var _newtarget = this.constructor;
const a = ()=>{
_newtarget;
};
}
}

View File

@ -0,0 +1,9 @@
class Foo {
constructor() {
new.target;
}
test() {
new.target;
}
}

View File

@ -0,0 +1,8 @@
class Foo {
constructor(){
this.constructor;
}
test() {
void 0;
}
}

View File

@ -0,0 +1,20 @@
class Foo {
constructor() {
new.target;
}
}
class Bar extends Foo {
constructor() {
// This is probably bad...
new.target;
super();
}
}
class Baz extends Foo {
constructor() {
super();
new.target;
}
}

View File

@ -0,0 +1,17 @@
class Foo {
constructor(){
this.constructor;
}
}
class Bar extends Foo {
constructor(){
this.constructor;
super();
}
}
class Baz extends Foo {
constructor(){
super();
this.constructor;
}
}

View File

@ -0,0 +1,11 @@
function Foo() {
new.target;
}
Foo.prototype.test = function() {
new.target;
};
var Bar = function() {
new.target;
};

View File

@ -0,0 +1,9 @@
function Foo() {
this instanceof Foo ? this.constructor : void 0;
}
Foo.prototype.test = function _target() {
this instanceof _target ? this.constructor : void 0;
};
var Bar = function _target1() {
this instanceof _target1 ? this.constructor : void 0;
};

View File

@ -0,0 +1,11 @@
"use strict";
const object = {
test() {
new.target;
},
test2: function() {
new.target;
},
}

View File

@ -0,0 +1,9 @@
"use strict";
const object = {
test () {
void 0;
},
test2: function _target() {
this instanceof _target ? this.constructor : void 0;
}
};

View File

@ -3,7 +3,7 @@ use anyhow::{bail, Context, Error};
use serde::de::DeserializeOwned;
use std::{
env,
fs::{create_dir_all, read_to_string, remove_dir_all, OpenOptions},
fs::{create_dir_all, read_to_string, OpenOptions},
io::{self, Write},
mem::{replace, take},
path::Path,
@ -428,9 +428,6 @@ where
.join("testing")
.join(test_name);
// Remove outputs from previous tests
let _ = remove_dir_all(&root);
create_dir_all(&root).expect("failed to create parent directory for temp directory");
let tmp_dir = tempdir_in(&root).expect("failed to create a temp directory");

View File

@ -45,7 +45,7 @@ impl SwcLoader {
.and_then(|g| Some(g.envs.clone()))
.unwrap_or_default();
let mut envs_map: HashMap<String, String> = envs
let envs_map: HashMap<String, String> = envs
.into_iter()
.map(|name| {
let value = env::var(&name).ok();