diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc b/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc
new file mode 100644
index 00000000000..37796ffcbab
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8275/input/.swcrc
@@ -0,0 +1,22 @@
+{
+ "jsc": {
+ "parser": {
+ "syntax": "ecmascript",
+ "jsx": false
+ },
+ "target": "es2022",
+ "loose": false,
+ "minify": {
+ "compress": {
+ "pure_funcs": [
+ "pure_html"
+ ]
+ },
+ "mangle": false
+ }
+ },
+ "module": {
+ "type": "es6"
+ },
+ "isModule": true
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js b/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js
new file mode 100644
index 00000000000..9300d994969
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8275/input/1.js
@@ -0,0 +1,15 @@
+/*#__PURE__*/
+function pure_html(strings, ...values) {
+ return { strings, values };
+}
+
+// ✅ This is tree-shaken from the minified output.
+const a = `aaaa`;
+
+// ❌ This is not tree-shaken from the minified output,
+// despite the "html" function being declare using
+// `jsc.minify.compress.pure_funcs` configuration.
+const b = pure_html`bbbb`;
+
+// ✅ This is not tree-shaken from the minified output.
+export const c = pure_html`cccc`;
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js b/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js
new file mode 100644
index 00000000000..41a9c9ad507
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-8xxx/8275/output/1.js
@@ -0,0 +1,6 @@
+export const c = (function(strings, ...values) {
+ return {
+ strings,
+ values
+ };
+})`cccc`;
diff --git a/crates/swc_ecma_minifier/src/compress/pure/misc.rs b/crates/swc_ecma_minifier/src/compress/pure/misc.rs
index 7e41aa1b651..1eee79a6f98 100644
--- a/crates/swc_ecma_minifier/src/compress/pure/misc.rs
+++ b/crates/swc_ecma_minifier/src/compress/pure/misc.rs
@@ -928,20 +928,35 @@ impl Pure<'_> {
_ => {}
}
- if let Expr::Call(CallExpr {
- callee: Callee::Expr(callee),
- args,
- ..
- }) = e
- {
- if callee.is_pure_callee(&self.expr_ctx) {
- self.changed = true;
- report_change!("Dropping pure call as callee is pure");
- *e = self
- .make_ignored_expr(args.take().into_iter().map(|arg| arg.expr))
- .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP }));
- return;
+ match e {
+ Expr::Call(CallExpr {
+ callee: Callee::Expr(callee),
+ args,
+ ..
+ }) => {
+ if callee.is_pure_callee(&self.expr_ctx) {
+ self.changed = true;
+ report_change!("Dropping pure call as callee is pure");
+ *e = self
+ .make_ignored_expr(args.take().into_iter().map(|arg| arg.expr))
+ .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP }));
+ return;
+ }
}
+
+ Expr::TaggedTpl(TaggedTpl {
+ tag: callee, tpl, ..
+ }) => {
+ if callee.is_pure_callee(&self.expr_ctx) {
+ self.changed = true;
+ report_change!("Dropping pure tag tpl as callee is pure");
+ *e = self
+ .make_ignored_expr(tpl.exprs.take().into_iter())
+ .unwrap_or(Expr::Invalid(Invalid { span: DUMMY_SP }));
+ return;
+ }
+ }
+ _ => (),
}
if self.options.unused {
diff --git a/crates/swc_ecma_minifier/src/metadata/mod.rs b/crates/swc_ecma_minifier/src/metadata/mod.rs
index ee353458e37..3d564b95da9 100644
--- a/crates/swc_ecma_minifier/src/metadata/mod.rs
+++ b/crates/swc_ecma_minifier/src/metadata/mod.rs
@@ -58,6 +58,39 @@ struct InfoMarker<'a> {
state: State,
}
+impl InfoMarker<'_> {
+ fn is_pure_callee(&self, callee: &Expr) -> bool {
+ match callee {
+ Expr::Ident(callee) => {
+ if self.pure_callee.contains(&callee.to_id()) {
+ return true;
+ }
+ }
+
+ Expr::Seq(callee) => {
+ if has_pure(self.comments, callee.span) {
+ return true;
+ }
+ }
+ _ => (),
+ }
+
+ if let Some(pure_fns) = &self.pure_funcs {
+ if let Expr::Ident(..) = callee {
+ // Check for pure_funcs
+ if Ident::within_ignored_ctxt(|| {
+ //
+ pure_fns.contains(&NodeIgnoringSpan::borrowed(callee))
+ }) {
+ return true;
+ }
+ }
+ }
+
+ has_pure(self.comments, callee.span())
+ }
+}
+
impl VisitMut for InfoMarker<'_> {
noop_visit_mut_type!();
@@ -71,19 +104,9 @@ impl VisitMut for InfoMarker<'_> {
// We check callee in some cases because we move comments
// See https://github.com/swc-project/swc/issues/7241
if match &n.callee {
- Callee::Expr(e) => match &**e {
- Expr::Ident(callee) => self.pure_callee.contains(&callee.to_id()),
- _ => false,
- },
+ Callee::Expr(e) => self.is_pure_callee(e),
_ => false,
} || has_pure(self.comments, n.span)
- || match &n.callee {
- Callee::Expr(e) => match &**e {
- Expr::Seq(callee) => has_pure(self.comments, callee.span),
- _ => false,
- },
- _ => false,
- }
{
if !n.span.is_dummy_ignoring_cmt() {
n.span = n.span.apply_mark(self.marks.pure);
@@ -185,6 +208,16 @@ impl VisitMut for InfoMarker<'_> {
}
}
+ fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
+ n.visit_mut_children_with(self);
+
+ if has_pure(self.comments, n.span) || self.is_pure_callee(&n.tag) {
+ if !n.span.is_dummy_ignoring_cmt() {
+ n.span = n.span.apply_mark(self.marks.pure);
+ }
+ }
+ }
+
fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
n.visit_mut_children_with(self);