diff --git a/crates/swc_ecma_minifier/tests/exec.rs b/crates/swc_ecma_minifier/tests/exec.rs index f920a4f360cc..9f9ea4183e78 100644 --- a/crates/swc_ecma_minifier/tests/exec.rs +++ b/crates/swc_ecma_minifier/tests/exec.rs @@ -292,6 +292,45 @@ fn run_default_exec_test(input_src: &str) { run_exec_test(input_src, config, false); } +fn run_compress_and_mangle_exec_test(input_src: &str, config: &str) { + let expected_output = stdout_of(input_src).unwrap(); + + testing::run_test2(false, |cm, handler| { + let _tracing = span!(Level::ERROR, "compress-and-mangle").entered(); + + let output = run( + cm.clone(), + &handler, + input_src, + Some(config), + Some(MangleOptions { + top_level: Some(true), + ..Default::default() + }), + ); + + let output = output.expect("Parsing in base test should not fail"); + let output = print(cm, &[&output], true, false); + + eprintln!( + "---- {} -----\n{}", + Color::Green.paint("Optimized code"), + output + ); + + let actual_output = stdout_of(&output).expect("failed to execute the optimized code"); + assert_ne!(actual_output, ""); + + assert_eq!( + DebugUsingDisplay(&actual_output), + DebugUsingDisplay(&expected_output) + ); + + Ok(()) + }) + .unwrap(); +} + #[test] fn concat_tpl_keeps_delimiter_after_interpolation() { run_default_exec_test( @@ -12363,6 +12402,88 @@ console.log(out.poisoned); run_default_exec_test(src); } +#[test] +fn issue_11977_iife_param_extraction_does_not_collide_with_var() { + let src = r#" +var d = class {}; +var __turbopack_context__ = { + z: function(value) { + return value; + } +}; +var ErrorType = 1; +var i = {}; +var serverOnlyRequire; +var Subscription = function() { + function Subscription1(listeners, listener) {} + Subscription1.prototype.add = function(subscription) { + if (this.unsubscribed) {} + }; +}(); +try { + serverOnlyRequire = eval('require'); +} catch (err) {} +var __require = /* @__PURE__ */ ((x)=>("TURBOPACK compile-time truthy", 1) ? __turbopack_context__.z : "TURBOPACK unreachable")(function(x) { +})(ErrorType || {}); +var isRequestError = (error)=>{ + let u = i.getKey ?? ((p, s)=>`x`), f = async ()=>{ + try {} catch (s) {} + }; +}; +var x = class extends d {}; +console.log(typeof x, x === x); +"#; + let config = r#"{ + "defaults": true, + "toplevel": true + }"#; + + run_compress_and_mangle_exec_test(src, config); +} + +#[test] +fn issue_11977_iife_param_extraction_does_not_collide_in_function() { + let src = r#" +var g = {}; +var d = class {}; +var __turbopack_context__ = { + z: function(value) { + return value; + } +}; +var ErrorType = 1; +var i = {}; +var serverOnlyRequire; +g.foo = function() { +var Subscription = function() { + function Subscription1(listeners, listener) {} + Subscription1.prototype.add = function(subscription) { + if (this.unsubscribed) {} + }; +}(); +try { + serverOnlyRequire = eval('require'); +} catch (err) {} +var __require = /* @__PURE__ */ ((x)=>("TURBOPACK compile-time truthy", 1) ? __turbopack_context__.z : "TURBOPACK unreachable")(function(x) { +})(ErrorType || {}); +var isRequestError = (error)=>{ + let u = i.getKey ?? ((p, s)=>`x`), f = async ()=>{ + try {} catch (s) {} + }; +}; +var x = class extends d {}; +console.log(typeof x, x === x); +}; +g.foo(); +"#; + let config = r#"{ + "defaults": true, + "toplevel": true + }"#; + + run_compress_and_mangle_exec_test(src, config); +} + #[test] fn issue_11294_eval_mangle_no_collision() { // Regression test for #11294. diff --git a/crates/swc_ecma_transforms_base/src/rename/analyer_and_collector.rs b/crates/swc_ecma_transforms_base/src/rename/analyer_and_collector.rs index 63756bdd47d3..cc8fc3bd19e0 100644 --- a/crates/swc_ecma_transforms_base/src/rename/analyer_and_collector.rs +++ b/crates/swc_ecma_transforms_base/src/rename/analyer_and_collector.rs @@ -87,7 +87,7 @@ impl Visit for AnalyzerAndCollector { let old_decl_collector_is_pat_decl = self.decl_collector.is_pat_decl; self.decl_collector.is_pat_decl = true; - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.ctxt); let old_analyzer_is_pat_decl = self.analyzer.is_pat_decl; self.analyzer.is_pat_decl = true; @@ -133,7 +133,7 @@ impl Visit for AnalyzerAndCollector { } fn visit_block_stmt(&mut self, node: &BlockStmt) { - let old_analyzer = self.analyzer.enter_block_scope(); + let old_analyzer = self.analyzer.enter_block_scope(Some(node.ctxt)); node.visit_children_with(self); @@ -147,7 +147,7 @@ impl Visit for AnalyzerAndCollector { fn visit_catch_clause(&mut self, node: &CatchClause) { let old_decl_collector_is_pat_decl = self.decl_collector.is_pat_decl; - let old_analyzer = self.analyzer.enter_block_scope(); + let old_analyzer = self.analyzer.enter_block_scope(Some(node.body.ctxt)); let old_analyzer_is_pat_decl = self.analyzer.is_pat_decl; let old_analyzer_in_catch_params = self.analyzer.in_catch_params; @@ -179,7 +179,7 @@ impl Visit for AnalyzerAndCollector { } fn visit_class_expr(&mut self, node: &ClassExpr) { - let old_analyzer = self.analyzer.enter_block_scope(); + let old_analyzer = self.analyzer.enter_block_scope(Some(node.class.ctxt)); self.analyzer.handle_class_expr(node); @@ -193,7 +193,7 @@ impl Visit for AnalyzerAndCollector { fn visit_class_method(&mut self, node: &ClassMethod) { node.key.visit_with(self); - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.function.ctxt); node.function.decorators.visit_with(self); node.function.params.visit_with(self); @@ -205,7 +205,7 @@ impl Visit for AnalyzerAndCollector { } fn visit_constructor(&mut self, node: &Constructor) { - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.ctxt); node.key.visit_with(self); node.params.visit_with(self); if let Some(body) = &node.body { @@ -221,7 +221,7 @@ impl Visit for AnalyzerAndCollector { self.analyzer.handle_class_expr(c); self.decl_collector.handle_class_expr(c); - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(c.class.ctxt); c.visit_children_with(self); self.analyzer.exit_scope(old_analyzer); } @@ -293,7 +293,7 @@ impl Visit for AnalyzerAndCollector { node.ident.visit_with(self); - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.function.ctxt); if !need_skip_analyzer_record && has_rest { // self.analyer is different from above because we are in a function scope @@ -312,9 +312,9 @@ impl Visit for AnalyzerAndCollector { fn visit_fn_expr(&mut self, node: &FnExpr) { if let Some(id) = &node.ident { - let old_analyzer0 = self.analyzer.enter_fn_scope(); + let old_analyzer0 = self.analyzer.enter_fn_scope(id.ctxt); self.analyzer.add_decl(id.to_id(), true); - let old_analyzer1 = self.analyzer.enter_fn_scope(); + let old_analyzer1 = self.analyzer.enter_fn_scope(node.function.ctxt); // https://github.com/swc-project/swc/issues/6819 // // We need to check for assign pattern because safari has a bug. @@ -348,10 +348,10 @@ impl Visit for AnalyzerAndCollector { } fn visit_for_in_stmt(&mut self, node: &ForInStmt) { - let old_analyzer0 = self.analyzer.enter_block_scope(); + let old_analyzer0 = self.analyzer.enter_block_scope(None); node.left.visit_with(self); node.right.visit_with(self); - let old_analyzer1 = self.analyzer.enter_block_scope(); + let old_analyzer1 = self.analyzer.enter_block_scope(None); match node.body.as_ref() { Stmt::Block(n) => n.visit_children_with(self), _ => node.body.visit_with(self), @@ -361,10 +361,10 @@ impl Visit for AnalyzerAndCollector { } fn visit_for_of_stmt(&mut self, node: &ForOfStmt) { - let old_analyzer0 = self.analyzer.enter_block_scope(); + let old_analyzer0 = self.analyzer.enter_block_scope(None); node.left.visit_with(self); node.right.visit_with(self); - let old_analyzer1 = self.analyzer.enter_block_scope(); + let old_analyzer1 = self.analyzer.enter_block_scope(None); match node.body.as_ref() { Stmt::Block(n) => n.visit_children_with(self), _ => node.body.visit_with(self), @@ -374,11 +374,11 @@ impl Visit for AnalyzerAndCollector { } fn visit_for_stmt(&mut self, node: &ForStmt) { - let old_analyzer0 = self.analyzer.enter_block_scope(); + let old_analyzer0 = self.analyzer.enter_block_scope(None); node.init.visit_with(self); node.test.visit_with(self); node.update.visit_with(self); - let old_analyzer1 = self.analyzer.enter_block_scope(); + let old_analyzer1 = self.analyzer.enter_block_scope(None); match node.body.as_ref() { Stmt::Block(n) => n.visit_children_with(self), _ => node.body.visit_with(self), @@ -388,7 +388,7 @@ impl Visit for AnalyzerAndCollector { } fn visit_function(&mut self, node: &Function) { - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.ctxt); node.decorators.visit_with(self); node.params.visit_with(self); @@ -447,7 +447,7 @@ impl Visit for AnalyzerAndCollector { } fn visit_static_block(&mut self, node: &StaticBlock) { - let old_analyzer = self.analyzer.enter_fn_scope(); + let old_analyzer = self.analyzer.enter_fn_scope(node.body.ctxt); node.body.visit_children_with(self); diff --git a/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs b/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs index 3271c2be6904..ee485d273f29 100644 --- a/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs +++ b/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs @@ -1,4 +1,4 @@ -use swc_common::Mark; +use swc_common::{Mark, SyntaxContext}; use swc_ecma_ast::*; use self::scope::{Scope, ScopeKind}; @@ -18,6 +18,12 @@ pub(super) struct Analyzer { pub(super) var_belong_to_fn_scope: bool, pub(super) in_catch_params: bool, pub(super) scope: Scope, + /// Context for declarations that belong directly to `scope`. + /// + /// With direct `eval`, declarations created from the same source scope must + /// keep their printed names. Declarations moved from an inner scope are + /// still safe to rename, and the context lets us distinguish the two. + pub(super) scope_ctxt: Option, /// If we try add variables declared by `var` to the block scope, /// variables will be added to `hoisted_vars` and merged to latest /// function scope in the end. @@ -43,6 +49,7 @@ impl Analyzer { top_level_mark, skip_first_fn_or_class_decl, is_first_node: true, + scope_ctxt: Some(SyntaxContext::empty().apply_mark(top_level_mark)), hoisted_vars: Vec::with_capacity(32), mangle, ..Default::default() @@ -53,12 +60,14 @@ impl Analyzer { if belong_to_fn_scope { match self.scope.kind { ScopeKind::Fn => { - self.scope.add_decl(&id, self.has_eval, self.top_level_mark); + self.scope + .add_decl(&id, self.has_eval, self.top_level_mark, self.scope_ctxt); } ScopeKind::Block => self.hoisted_vars.push(id), } } else { - self.scope.add_decl(&id, self.has_eval, self.top_level_mark); + self.scope + .add_decl(&id, self.has_eval, self.top_level_mark, self.scope_ctxt); } } @@ -85,10 +94,11 @@ impl Analyzer { self.scope.reserve_usage(len); } - fn enter_scope(&mut self, kind: ScopeKind) -> Self { + fn enter_scope(&mut self, kind: ScopeKind, scope_ctxt: Option) -> Self { let mut analyer = Analyzer { has_eval: self.has_eval, top_level_mark: self.top_level_mark, + scope_ctxt, is_pat_decl: self.is_pat_decl, var_belong_to_fn_scope: false, in_catch_params: false, @@ -105,12 +115,12 @@ impl Analyzer { analyer // old analyzer } - pub(super) fn enter_fn_scope(&mut self) -> Self { - self.enter_scope(ScopeKind::Fn) + pub(super) fn enter_fn_scope(&mut self, scope_ctxt: SyntaxContext) -> Self { + self.enter_scope(ScopeKind::Fn, Some(scope_ctxt)) } - pub(super) fn enter_block_scope(&mut self) -> Self { - self.enter_scope(ScopeKind::Block) + pub(super) fn enter_block_scope(&mut self, scope_ctxt: Option) -> Self { + self.enter_scope(ScopeKind::Block, scope_ctxt) } pub(super) fn exit_scope(&mut self, mut v: Self) { diff --git a/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs b/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs index 4e1f92deecd8..bc4b6698e272 100644 --- a/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs +++ b/crates/swc_ecma_transforms_base/src/rename/analyzer/scope.rs @@ -7,7 +7,7 @@ use indexmap::IndexSet; use par_iter::prelude::*; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use swc_atoms::{atom, Atom}; -use swc_common::Mark; +use swc_common::{Mark, SyntaxContext}; use swc_ecma_ast::*; use tracing::debug; @@ -41,11 +41,19 @@ pub(super) struct ScopeData { /// because we merge every items in children to current scope. all: FxHashSet, + preserved: FxHashSet, + queue: FxIndexSet, } impl Scope { - pub(super) fn add_decl(&mut self, id: &Id, has_eval: bool, top_level_mark: Mark) { + pub(super) fn add_decl( + &mut self, + id: &Id, + has_eval: bool, + top_level_mark: Mark, + eval_preserve_ctxt: Option, + ) { if id.0 == atom!("arguments") { return; } @@ -53,8 +61,16 @@ impl Scope { self.data.all.insert(id.clone()); if !self.data.queue.contains(id) { - if has_eval && id.1.outer().is_descendant_of(top_level_mark) { - return; + if has_eval { + let should_preserve = match eval_preserve_ctxt { + Some(ctxt) => id.1 == ctxt, + None => id.1.outer().is_descendant_of(top_level_mark), + }; + + if should_preserve { + self.data.preserved.insert(id.clone()); + return; + } } self.data.queue.insert(id.clone()); @@ -103,6 +119,7 @@ impl Scope { let queue = take(&mut self.data.queue); // let mut cloned_reverse = reverse.clone(); + self.reserve_preserved_symbols(reverse); self.rename_one_scope_in_normal_mode( renamer, @@ -220,6 +237,7 @@ impl Scope { let queue = take(&mut self.data.queue); let mut cloned_reverse = reverse.next(); + self.reserve_preserved_symbols(&mut cloned_reverse); self.rename_one_scope_in_mangle_mode( renamer, @@ -331,4 +349,10 @@ impl Scope { let children = &self.children; self.data.queue.len() + children.iter().map(|v| v.rename_cost()).sum::() } + + fn reserve_preserved_symbols(&self, reverse: &mut ReverseMap) { + for id in &self.data.preserved { + reverse.push_entry(id.0.clone(), id.clone()); + } + } }