Repository: swc-project/swc (rust)

Issue:
pure_funcs is not working when minify
### Describe the bug

pure_funcs is not working when setup config for minify code

### Input code

```typescript
const { code } = await swc.minify(`console.log('__LOG__');console.warn('__WARN__');`, {
  compress: { pure_funcs: ['console.log']}
})
```


### Config

_No response_

### Playground link

_No response_

### Expected behavior

remove code of `console.log('__LOG__')`

### Actual behavior

_No response_

### Version

1.2.170

### Additional context

_No response_

Pull request description:
feat(es/minifier): Implement `pure_funcs`
**Description:**


**Related issue (if exists):**

 - Closes #4396 

Patch:
diff --git a/crates/swc_ecma_minifier/src/lib.rs b/crates/swc_ecma_minifier/src/lib.rs
index 9326c33cf7..7594de79eb 100644
--- a/crates/swc_ecma_minifier/src/lib.rs
+++ b/crates/swc_ecma_minifier/src/lib.rs
@@ -105,7 +105,12 @@ pub fn optimize(
     }
 
     if options.compress.is_some() {
-        m.visit_mut_with(&mut info_marker(comments, marks, extra.unresolved_mark));
+        m.visit_mut_with(&mut info_marker(
+            options.compress.as_ref(),
+            comments,
+            marks,
+            extra.unresolved_mark,
+        ));
     }
     m.visit_mut_with(&mut unique_scope());
 
diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs
index 2f9bf09e5b..2a17d48a10 100644
--- a/crates/swc_ecma_minifier/src/metadata/mod.rs
+++ b/crates/swc_ecma_minifier/src/metadata/mod.rs
@@ -1,6 +1,6 @@
 use swc_common::{
     comments::{Comment, CommentKind, Comments},
-    Mark, Span, SyntaxContext,
+    EqIgnoreSpan, Mark, Span, SyntaxContext,
 };
 use swc_ecma_ast::*;
 use swc_ecma_utils::find_pat_ids;
@@ -8,18 +8,20 @@ use swc_ecma_visit::{
     noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
 };
 
-use crate::marks::Marks;
+use crate::{marks::Marks, option::CompressOptions};
 
 #[cfg(test)]
 mod tests;
 
 /// This pass analyzes the comment and convert it to a mark.
-pub(crate) fn info_marker(
-    comments: Option<&dyn Comments>,
+pub(crate) fn info_marker<'a>(
+    options: Option<&'a CompressOptions>,
+    comments: Option<&'a dyn Comments>,
     marks: Marks,
     unresolved_mark: Mark,
-) -> impl '_ + VisitMut {
+) -> impl 'a + VisitMut {
     InfoMarker {
+        options,
         comments,
         marks,
         unresolved_mark,
@@ -34,6 +36,8 @@ struct State {
 }
 
 struct InfoMarker<'a> {
+    options: Option<&'a CompressOptions>,
+
     comments: Option<&'a dyn Comments>,
     marks: Marks,
     unresolved_mark: Mark,
@@ -119,6 +123,18 @@ impl VisitMut for InfoMarker<'_> {
 
         if self.has_pure(n.span) {
             n.span = n.span.apply_mark(self.marks.pure);
+        } else if let Some(options) = self.options {
+            if let Callee::Expr(e) = &n.callee {
+                // Check for pure_funcs
+
+                if options
+                    .pure_funcs
+                    .iter()
+                    .any(|pure_func| Ident::within_ignored_ctxt(|| e.eq_ignore_span(pure_func)))
+                {
+                    n.span = n.span.apply_mark(self.marks.pure);
+                }
+            }
         }
     }
 
diff --git a/crates/swc_ecma_minifier/src/option/mod.rs b/crates/swc_ecma_minifier/src/option/mod.rs
index 2deab01c04..9875297404 100644
--- a/crates/swc_ecma_minifier/src/option/mod.rs
+++ b/crates/swc_ecma_minifier/src/option/mod.rs
@@ -250,10 +250,13 @@ pub struct CompressOptions {
     pub props: bool,
 
     #[serde(default)]
-    #[serde(alias = "properties")]
+    #[serde(alias = "pure_getters")]
     pub pure_getters: PureGetterOption,
 
-    // pure_funcs    : null,
+    #[serde(default)]
+    #[serde(alias = "pure_funcs")]
+    pub pure_funcs: Vec<Box<Expr>>,
+
     #[serde(default)]
     #[serde(alias = "reduce_funcs")]
     pub reduce_fns: bool,
diff --git a/crates/swc_ecma_minifier/src/option/terser.rs b/crates/swc_ecma_minifier/src/option/terser.rs
index e49d0fb4ab..01b7349ce0 100644
--- a/crates/swc_ecma_minifier/src/option/terser.rs
+++ b/crates/swc_ecma_minifier/src/option/terser.rs
@@ -385,6 +385,28 @@ impl TerserCompressorOptions {
             unused: self.unused.unwrap_or(self.defaults),
             const_to_let: self.const_to_let.unwrap_or(self.defaults),
             pristine_globals: self.pristine_globals.unwrap_or(self.defaults),
+            pure_funcs: self
+                .pure_funcs
+                .into_iter()
+                .map(|input| {
+                    let fm = cm.new_source_file(FileName::Anon, input);
+
+                    parse_file_as_expr(
+                        &fm,
+                        Default::default(),
+                        Default::default(),
+                        None,
+                        &mut vec![],
+                    )
+                    .map(drop_span)
+                    .unwrap_or_else(|err| {
+                        panic!(
+                            "failed to parse `pure_funcs` of minifier options: {:?}",
+                            err
+                        )
+                    })
+                })
+                .collect(),
         }
     }
 }


Test patch:
diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs
index 610fd5a8d7..85b70b2708 100644
--- a/crates/swc_ecma_minifier/tests/exec.rs
+++ b/crates/swc_ecma_minifier/tests/exec.rs
@@ -9213,6 +9213,7 @@ g = 42;
 }
 
 #[test]
+#[ignore]
 fn terser_pure_funcs_issue_3065_4() {
     let src = r###"var debug = function (msg) {
     console.log(msg);
@@ -9235,6 +9236,7 @@ debug(
 }
 
 #[test]
+#[ignore]
 fn terser_pure_funcs_issue_3065_3() {
     let src = r###"function debug(msg) {
     console.log(msg);
diff --git a/crates/swc_ecma_minifier/tests/golden.txt b/crates/swc_ecma_minifier/tests/golden.txt
index d2b71e0d5e..61a1318d21 100644
--- a/crates/swc_ecma_minifier/tests/golden.txt
+++ b/crates/swc_ecma_minifier/tests/golden.txt
@@ -1031,6 +1031,13 @@ properties/mangle_properties_which_matches_pattern/input.js
 properties/native_prototype_lhs/input.js
 pure_funcs/array/input.js
 pure_funcs/assign/input.js
+pure_funcs/babel/input.js
+pure_funcs/issue_3065_1/input.js
+pure_funcs/issue_3065_2/input.js
+pure_funcs/issue_3065_3/input.js
+pure_funcs/issue_3065_4/input.js
+pure_funcs/side_effects/input.js
+pure_funcs/unused/input.js
 pure_getters/chained/input.js
 pure_getters/collapse_rhs_call/input.js
 pure_getters/collapse_rhs_false/input.js
diff --git a/crates/swc_ecma_minifier/tests/postponed.txt b/crates/swc_ecma_minifier/tests/postponed.txt
index 50b67eb935..0959044f29 100644
--- a/crates/swc_ecma_minifier/tests/postponed.txt
+++ b/crates/swc_ecma_minifier/tests/postponed.txt
@@ -539,7 +539,6 @@ properties/skip_undeclared_properties_by_default/input.js
 properties/sub_properties/input.js
 properties/unsafe_methods_regex/input.js
 pure_funcs/arithmetic/input.js
-pure_funcs/babel/input.js
 pure_funcs/boolean_and/input.js
 pure_funcs/boolean_or/input.js
 pure_funcs/conditional/input.js
@@ -556,15 +555,9 @@ pure_funcs/issue_2705_3/input.js
 pure_funcs/issue_2705_4/input.js
 pure_funcs/issue_2705_5/input.js
 pure_funcs/issue_2705_6/input.js
-pure_funcs/issue_3065_1/input.js
-pure_funcs/issue_3065_2/input.js
 pure_funcs/issue_3065_2b/input.js
-pure_funcs/issue_3065_3/input.js
-pure_funcs/issue_3065_4/input.js
 pure_funcs/issue_526_1/input.js
 pure_funcs/relational/input.js
-pure_funcs/side_effects/input.js
-pure_funcs/unused/input.js
 pure_getters/collapse_vars_1_true/input.js
 pure_getters/collapse_vars_2_true/input.js
 pure_getters/impure_getter_2/input.js