Repository: swc-project/swc (rust)

Issue:
`new.target` in arrow function wrongly transformed 
### Describe the bug

This is similar to #2811, the problem lies in chrome 46 support `new.target` but not `arrow`

### Input code

```typescript
function foo() {
  const a = (a) => new.target
}
```


### Config

```js
{
  jsc: {
    parser: {
      syntax: "ecmascript"
    }
  },
  env: {
    targets: {
      chrome: "46"
    },
    mode: "entry",
    coreJs: 3
  }
}
```


### Playground link

_No response_

### Expected behavior

should output
```js
function foo() {
  var _newtarget = new.target;

  var a = function a(_a) {
    return _newtarget;
  };
}
```

### Version

1.2.112

### Additional context

There are two solutions.

1. Remove arrow related transform in `new.target` and move it to `arrow`
   pros: it's clean and can be reused in other transforms
   cons: it will need `arrow` transform be placed before `new.target`
2. Make `new.target` aware arrow function don't have its own `new.target` and reuse it in other transforms
   cons: it's ugly

Pull request description:
fix(ecma/transform/compat): transform new.target in arrow
**Related issue (if exists):**

closes #2885 

Also why `new_target` transform not in `preset_env`?


Patch:
diff --git a/crates/swc_ecma_preset_env/src/lib.rs b/crates/swc_ecma_preset_env/src/lib.rs
index a318c81304..e8ca0ba75b 100644
--- a/crates/swc_ecma_preset_env/src/lib.rs
+++ b/crates/swc_ecma_preset_env/src/lib.rs
@@ -200,12 +200,13 @@ where
     );
     let pass = add!(pass, BlockScoping, es2015::block_scoping(), true);
 
+    let pass = add!(pass, NewTarget, es2015::new_target(), true);
+
     // TODO:
     //    Literals,
     //    ObjectSuper,
     //    DotAllRegex,
     //    UnicodeRegex,
-    //    NewTarget,
     //    AsyncGeneratorFunctions,
     //    UnicodePropertyRegex,
     //    JsonStrings,
diff --git a/crates/swc_ecma_utils/src/function.rs b/crates/swc_ecma_utils/src/function.rs
index 6cba16a1bc..7e4484f57d 100644
--- a/crates/swc_ecma_utils/src/function.rs
+++ b/crates/swc_ecma_utils/src/function.rs
@@ -7,16 +7,25 @@ use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
 pub struct WrapperState {
     this: Option<Ident>,
     args: Option<Ident>,
+    new_target: Option<Ident>,
 }
 
 impl WrapperState {
-    pub fn new(this: Option<Ident>, args: Option<Ident>) -> Self {
-        Self { this, args }
+    pub fn new(this: Option<Ident>, args: Option<Ident>, new_target: Option<Ident>) -> Self {
+        Self {
+            this,
+            args,
+            new_target,
+        }
     }
     pub fn to_decl(self) -> Vec<VarDeclarator> {
-        let Self { this, args } = self;
+        let Self {
+            this,
+            args,
+            new_target,
+        } = self;
 
-        let mut decls = Vec::with_capacity(2);
+        let mut decls = Vec::with_capacity(3);
         if let Some(this_id) = this {
             decls.push(VarDeclarator {
                 span: DUMMY_SP,
@@ -36,6 +45,17 @@ impl WrapperState {
                 definite: false,
             });
         }
+        if let Some(id) = new_target {
+            decls.push(VarDeclarator {
+                span: DUMMY_SP,
+                name: Pat::Ident(id.into()),
+                init: Some(Box::new(Expr::MetaProp(MetaPropExpr {
+                    meta: Ident::new(js_word!("new"), DUMMY_SP),
+                    prop: Ident::new(js_word!("target"), DUMMY_SP),
+                }))),
+                definite: false,
+            });
+        }
         decls
     }
     pub fn to_stmt(self) -> Option<Stmt> {
@@ -54,9 +74,13 @@ impl WrapperState {
     }
 
     pub fn to_stmt_in_subclass(self) -> (Option<Stmt>, Option<Ident>) {
-        let Self { this, args } = self;
+        let Self {
+            this,
+            args,
+            new_target,
+        } = self;
 
-        let mut decls = Vec::with_capacity(2);
+        let mut decls = Vec::with_capacity(3);
         if let Some(this_id) = &this {
             decls.push(VarDeclarator {
                 span: DUMMY_SP,
@@ -76,6 +100,17 @@ impl WrapperState {
                 definite: false,
             });
         }
+        if let Some(id) = new_target {
+            decls.push(VarDeclarator {
+                span: DUMMY_SP,
+                name: Pat::Ident(id.into()),
+                init: Some(Box::new(Expr::MetaProp(MetaPropExpr {
+                    meta: Ident::new(js_word!("new"), DUMMY_SP),
+                    prop: Ident::new(js_word!("target"), DUMMY_SP),
+                }))),
+                definite: false,
+            });
+        }
 
         if decls.is_empty() {
             (None, None)
@@ -128,6 +163,24 @@ impl<'a> VisitMut for FunctionWrapper<'a> {
                     .get_or_insert_with(|| private_ident!("_this"));
                 *e = Expr::Ident(this.clone());
             }
+            Expr::MetaProp(MetaPropExpr {
+                meta:
+                    Ident {
+                        sym: js_word!("new"),
+                        ..
+                    },
+                prop:
+                    Ident {
+                        sym: js_word!("target"),
+                        ..
+                    },
+            }) => {
+                let target = self
+                    .state
+                    .new_target
+                    .get_or_insert_with(|| private_ident!("_newtarget"));
+                *e = Expr::Ident(target.clone());
+            }
             _ => e.visit_mut_children_with(self),
         }
     }


Test patch:
diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_arrow.rs b/crates/swc_ecma_transforms_compat/tests/es2015_arrow.rs
index 41555d867d..30455f851f 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2015_arrow.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2015_arrow.rs
@@ -507,3 +507,19 @@ const b = function() {
 };
 "#
 );
+
+test!(
+    ::swc_ecma_parser::Syntax::default(),
+    |_| arrow(),
+    chrome_46,
+    "function foo() {
+      const a = (a) => new.target
+    }",
+    "function foo() {
+      var _newtarget = new.target;
+
+      const a = function (a) {
+        return _newtarget;
+      };
+    }"
+);
diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_new_target.rs b/crates/swc_ecma_transforms_compat/tests/es2015_new_target.rs
index 2bbdc9f470..a03b5a3fba 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2015_new_target.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2015_new_target.rs
@@ -1,6 +1,6 @@
 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};
+use swc_ecma_transforms_testing::{exec_tr, test, test_fixture};
 
 #[testing::fixture("tests/fixture/new-target/**/exec.js")]
 fn exec(input: PathBuf) {
@@ -14,3 +14,16 @@ fn fixture(input: PathBuf) {
     let output = dir.join("output.js");
     test_fixture(Default::default(), &|_| new_target(), &input, &output);
 }
+
+test!(
+    ::swc_ecma_parser::Syntax::default(),
+    |_| new_target(),
+    edge_12,
+    r#"function foo() {
+        const a = () => new.target
+    }"#,
+    r#"function foo() {
+        var _newtarget = this instanceof foo ? this.constructor : void 0;
+        const a = () => _newtarget
+    }"#
+);