Repository: swc-project/swc (rust)

Issue:
new.target syntax is not transformed
**Describe the bug**

[`new.target`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target) is not being transformed to ES5-compatible syntax when `es5` is the target.

**Input code**
[Please see this repro](https://repl.it/@wessberg/swc-newtarget#index.js).

In it, you'll find that `output.js` is the result of running `swc index.js -o output.js` with `es5` as the target.

**Config**
You'll find it in the repro, but here it is:
```json
{
  "jsc": {
    "target": "es5"
  }
}
```

**Expected behavior**
`new.target` syntax should be transpiled into syntax that legacy environments can understand.

Here's how babel would have rewritten new.target in the example from the repro:

```js
if ((this instanceof Foo ? this.constructor : void 0) === undefined) {
    throw new TypeError("Constructor ".concat(Foo.name, " requires 'new'"));
}
```

**Version**
The version of @swc/core: 1.2.36

Pull request description:
fix(es/transforms/compat): Implement `new.tartet`
swc_ecma_transforms_compat:
 - [x] Handle `new.target`. (Closes #1179)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5644378faa..473e26c4bc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/ecmascript/parser/Cargo.toml b/ecmascript/parser/Cargo.toml
index 309148a21b..b2f0241009 100644
--- a/ecmascript/parser/Cargo.toml
+++ b/ecmascript/parser/Cargo.toml
@@ -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
diff --git a/ecmascript/transforms/compat/Cargo.toml b/ecmascript/transforms/compat/Cargo.toml
index f34b897c68..57c9fa31fd 100644
--- a/ecmascript/transforms/compat/Cargo.toml
+++ b/ecmascript/transforms/compat/Cargo.toml
@@ -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]
diff --git a/ecmascript/transforms/compat/src/es2015/mod.rs b/ecmascript/transforms/compat/src/es2015/mod.rs
index 7e0418022a..dde2c74c5c 100644
--- a/ecmascript/transforms/compat/src/es2015/mod.rs
+++ b/ecmascript/transforms/compat/src/es2015/mod.rs
@@ -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;
diff --git a/ecmascript/transforms/compat/src/es2015/new_target.rs b/ecmascript/transforms/compat/src/es2015/new_target.rs
new file mode 100644
index 0000000000..5dd404ea77
--- /dev/null
+++ b/ecmascript/transforms/compat/src/es2015/new_target.rs
@@ -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
+    }
+}
diff --git a/node/bundler/src/loaders/swc.rs b/node/bundler/src/loaders/swc.rs
index 658c2ce5c2..02639eb299 100644
--- a/node/bundler/src/loaders/swc.rs
+++ b/node/bundler/src/loaders/swc.rs
@@ -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();


Test patch:
diff --git a/ecmascript/transforms/compat/tests/es2015_new_target.rs b/ecmascript/transforms/compat/tests/es2015_new_target.rs
new file mode 100644
index 0000000000..2bbdc9f470
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/es2015_new_target.rs
@@ -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);
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/class-extended/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/class-extended/exec.js
new file mode 100644
index 0000000000..78eb0c33d5
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/class-extended/exec.js
@@ -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);
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/class/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/class/exec.js
new file mode 100644
index 0000000000..4651c76237
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/class/exec.js
@@ -0,0 +1,12 @@
+"use strict";
+
+const targets = [];
+class Foo {
+  constructor() {
+    targets.push(new.target);
+  }
+}
+
+new Foo;
+
+expect(targets[0]).toBe(Foo);
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class-extended/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class-extended/exec.js
new file mode 100644
index 0000000000..87071962cf
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class-extended/exec.js
@@ -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();
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class/exec.js
new file mode 100644
index 0000000000..f3d6f3066a
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function-class/exec.js
@@ -0,0 +1,10 @@
+"use strict";
+
+const targets = [];
+function Foo() {
+  targets.push(new.target);
+}
+
+new Foo;
+
+expect(targets[0]).toBe(Foo);
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/function/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function/exec.js
new file mode 100644
index 0000000000..082aa85505
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/function/exec.js
@@ -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();
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-class/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-class/exec.js
new file mode 100644
index 0000000000..f7fc3cf03f
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-class/exec.js
@@ -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);
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-function/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-function/exec.js
new file mode 100644
index 0000000000..1ed060abd0
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/exec/reflect-function/exec.js
@@ -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();
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/exec.js
new file mode 100644
index 0000000000..396babafae
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/exec.js
@@ -0,0 +1,9 @@
+class Foo {
+  constructor() {
+    this.Bar = class {
+      static p = new.target
+    }
+  }
+}
+
+expect((new Foo).Bar.p).toBeUndefined()
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/input.js
new file mode 100644
index 0000000000..0045e3f3d0
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/input.js
@@ -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;
+  }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/options.json b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/options.json
new file mode 100644
index 0000000000..fafe51d4fb
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/options.json
@@ -0,0 +1,7 @@
+{
+  "plugins": [
+    "transform-new-target",
+    "transform-arrow-functions",
+    ["proposal-class-properties", { "loose": true }]
+  ]
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/output.js
new file mode 100644
index 0000000000..a64af0349e
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties-loose/output.js
@@ -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);
+  }
+
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/exec.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/exec.js
new file mode 100644
index 0000000000..396babafae
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/exec.js
@@ -0,0 +1,9 @@
+class Foo {
+  constructor() {
+    this.Bar = class {
+      static p = new.target
+    }
+  }
+}
+
+expect((new Foo).Bar.p).toBeUndefined()
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/input.js
new file mode 100644
index 0000000000..0045e3f3d0
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/input.js
@@ -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;
+  }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/options.json b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/options.json
new file mode 100644
index 0000000000..e82809c941
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/options.json
@@ -0,0 +1,7 @@
+{
+  "plugins": [
+    "transform-new-target",
+    "transform-arrow-functions",
+    ["proposal-class-properties", { "loose": false }]
+  ]
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/output.js
new file mode 100644
index 0000000000..50c1d35350
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/.class-properties/output.js
@@ -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);
+  }
+
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/input.js
new file mode 100644
index 0000000000..902735c13a
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/input.js
@@ -0,0 +1,13 @@
+function Foo() {
+  const a = () => {
+    new.target;
+  };
+}
+
+class Bar {
+  constructor() {
+    const a = () => {
+      new.target;
+    };
+  }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/options.json b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/options.json
new file mode 100644
index 0000000000..a48be29804
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/options.json
@@ -0,0 +1,3 @@
+{
+  "plugins": ["transform-new-target", "transform-arrow-functions"]
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/output.js
new file mode 100644
index 0000000000..d8fb911a7c
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/arrow/output.js
@@ -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;
+        };
+    }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/class/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/class/input.js
new file mode 100644
index 0000000000..eb27600e91
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/class/input.js
@@ -0,0 +1,9 @@
+class Foo {
+  constructor() {
+    new.target;
+  }
+
+  test() {
+    new.target;
+  }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/class/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/class/output.js
new file mode 100644
index 0000000000..c95b4a1785
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/class/output.js
@@ -0,0 +1,8 @@
+class Foo {
+    constructor(){
+        this.constructor;
+    }
+    test() {
+        void 0;
+    }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/input.js
new file mode 100644
index 0000000000..420f160b66
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/input.js
@@ -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;
+  }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/output.js
new file mode 100644
index 0000000000..423422c676
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/extended-class/output.js
@@ -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;
+    }
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/function/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/function/input.js
new file mode 100644
index 0000000000..67fd631d5a
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/function/input.js
@@ -0,0 +1,11 @@
+function Foo() {
+  new.target;
+}
+
+Foo.prototype.test = function() {
+  new.target;
+};
+
+var Bar = function() {
+  new.target;
+};
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/function/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/function/output.js
new file mode 100644
index 0000000000..477f063b1a
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/function/output.js
@@ -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;
+};
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/object/input.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/object/input.js
new file mode 100644
index 0000000000..c231a30a58
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/object/input.js
@@ -0,0 +1,11 @@
+"use strict";
+
+const object = {
+  test() {
+    new.target;
+  },
+
+  test2: function() {
+    new.target;
+  },
+}
diff --git a/ecmascript/transforms/compat/tests/fixture/new-target/general/object/output.js b/ecmascript/transforms/compat/tests/fixture/new-target/general/object/output.js
new file mode 100644
index 0000000000..a431048250
--- /dev/null
+++ b/ecmascript/transforms/compat/tests/fixture/new-target/general/object/output.js
@@ -0,0 +1,9 @@
+"use strict";
+const object = {
+    test () {
+        void 0;
+    },
+    test2: function _target() {
+        this instanceof _target ? this.constructor : void 0;
+    }
+};
diff --git a/ecmascript/transforms/testing/src/lib.rs b/ecmascript/transforms/testing/src/lib.rs
index c5cfdbcb9c..553a4eadf2 100644
--- a/ecmascript/transforms/testing/src/lib.rs
+++ b/ecmascript/transforms/testing/src/lib.rs
@@ -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");