From 91d78000ea445575f9ac30d0f36299ab4f0cbf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Fri, 28 Jan 2022 16:08:41 +0900 Subject: [PATCH] feat(es/minifier): Implement `drop_console` (#3392) swc_ecma_minifier: - Merge `drop_console` into pure optimizer. - Implement `drop_console`. (Closes #2321) --- .../src/compress/drop_console.rs | 87 ----------------- crates/swc_ecma_minifier/src/compress/mod.rs | 13 +-- .../src/compress/optimize/loops.rs | 2 +- .../src/compress/optimize/mod.rs | 1 + .../src/compress/optimize/unused.rs | 87 ----------------- .../src/compress/pure/dead_code.rs | 94 +++++++++---------- .../src/compress/pure/drop_console.rs | 52 ++++++++++ .../src/compress/pure/if_return.rs | 36 +------ .../src/compress/pure/mod.rs | 11 ++- .../src/compress/util/mod.rs | 92 ++++++++++++++++++ crates/swc_ecma_minifier/tests/golden.txt | 3 + crates/swc_ecma_minifier/tests/ignored.txt | 3 - .../drop-console/drop_console_2/output.js | 2 - 13 files changed, 208 insertions(+), 275 deletions(-) delete mode 100644 crates/swc_ecma_minifier/src/compress/drop_console.rs create mode 100644 crates/swc_ecma_minifier/src/compress/pure/drop_console.rs diff --git a/crates/swc_ecma_minifier/src/compress/drop_console.rs b/crates/swc_ecma_minifier/src/compress/drop_console.rs deleted file mode 100644 index 67f75d140e0..00000000000 --- a/crates/swc_ecma_minifier/src/compress/drop_console.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::{borrow::Cow, mem::take}; -use swc_common::pass::CompilerPass; -use swc_ecma_ast::*; -use swc_ecma_transforms::pass::JsPass; -use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith}; - -pub fn drop_console() -> impl JsPass + VisitMut { - as_folder(DropConsole { done: false }) -} - -struct DropConsole { - /// Invoking this pass multiple times is simply waste of time. - done: bool, -} - -impl CompilerPass for DropConsole { - fn name() -> Cow<'static, str> { - "drop-console".into() - } -} - -impl VisitMut for DropConsole { - noop_visit_mut_type!(); - - fn visit_mut_expr(&mut self, n: &mut Expr) { - if self.done { - return; - } - - n.visit_mut_children_with(self); - - if let Expr::Call(CallExpr { - span, callee, args, .. - }) = n - { - // Find console.log - let callee = match callee { - Callee::Expr(callee) => callee, - _ => return, - }; - - if let Expr::Member(MemberExpr { - obj: callee_obj, - prop: MemberProp::Ident(_), - .. - }) = &**callee - { - let mut loop_co = &**callee_obj; - loop { - match loop_co { - Expr::Ident(obj) => { - if obj.sym != *"console" { - return; - } - break; - } - - Expr::Member(MemberExpr { - obj: loop_co_obj, - prop: MemberProp::Ident(_), - .. - }) => { - loop_co = loop_co_obj; - } - _ => return, - } - } - - // Simplifier will remove side-effect-free items. - *n = Expr::Seq(SeqExpr { - span: *span, - exprs: take(args).into_iter().map(|arg| arg.expr).collect(), - }) - } - } - } - - fn visit_mut_module(&mut self, n: &mut Module) { - if self.done { - return; - } - - n.visit_mut_children_with(self); - - self.done = true; - } -} diff --git a/crates/swc_ecma_minifier/src/compress/mod.rs b/crates/swc_ecma_minifier/src/compress/mod.rs index 13180e0f72d..f9813d490d9 100644 --- a/crates/swc_ecma_minifier/src/compress/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/mod.rs @@ -1,5 +1,5 @@ pub(crate) use self::pure::pure_optimizer; -use self::{drop_console::drop_console, hoist_decls::DeclHoisterConfig, optimize::optimizer}; +use self::{hoist_decls::DeclHoisterConfig, optimize::optimizer}; use crate::{ analyzer::{analyze, UsageAnalyzer}, compress::hoist_decls::decl_hoister, @@ -35,7 +35,6 @@ use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith, Vis use swc_timer::timer; use tracing::error; -mod drop_console; mod hoist_decls; mod optimize; mod pure; @@ -50,10 +49,6 @@ pub(crate) fn compressor<'a, M>( where M: Mode, { - let console_remover = Optional { - enabled: options.drop_console, - visitor: drop_console(), - }; let compressor = Compressor { globals, marks, @@ -66,9 +61,11 @@ where }; chain!( - console_remover, as_folder(compressor), - expr_simplifier(ExprSimplifierConfig {}) + Optional { + enabled: options.evaluate || options.side_effects, + visitor: expr_simplifier(ExprSimplifierConfig {}) + } ) } diff --git a/crates/swc_ecma_minifier/src/compress/optimize/loops.rs b/crates/swc_ecma_minifier/src/compress/optimize/loops.rs index 8aef3bad5f1..6fccfc5e649 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/loops.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/loops.rs @@ -1,5 +1,5 @@ use crate::{ - compress::optimize::{unused::UnreachableHandler, Optimizer}, + compress::{optimize::Optimizer, util::UnreachableHandler}, mode::Mode, }; use swc_common::{util::take::Take, DUMMY_SP}; diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index c0c8f2c9209..c5da8c17c1d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -2635,6 +2635,7 @@ where Expr::Lit(Lit::Num(..)) => {} _ => { + tracing::debug!("Ignoring arg of `void`"); let arg = self.ignore_return_value(&mut n.arg); n.arg = Box::new(arg.unwrap_or_else(|| make_number(DUMMY_SP, 0.0))); diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 81f022d9eb7..1218002017f 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -7,7 +7,6 @@ use swc_atoms::js_word; use swc_common::{util::take::Take, Span, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{contains_ident_ref, ident::IdentLike}; -use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; /// Methods related to the option `unused`. impl Optimizer<'_, M> @@ -553,89 +552,3 @@ where } } } - -#[derive(Debug, Default)] -pub(super) struct UnreachableHandler { - vars: Vec, - in_var_name: bool, - in_hoisted_var: bool, -} - -impl UnreachableHandler { - /// Assumes `s` is not reachable, and preserves variable declarations and - /// function declarations in `s`. - /// - /// Returns true if statement is changed. - pub fn preserve_vars(s: &mut Stmt) -> bool { - if s.is_empty() { - return false; - } - if let Stmt::Decl(Decl::Var(v)) = s { - let mut changed = false; - for decl in &mut v.decls { - if decl.init.is_some() { - decl.init = None; - changed = true; - } - } - - return changed; - } - - let mut v = Self::default(); - s.visit_mut_with(&mut v); - if v.vars.is_empty() { - *s = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); - } else { - *s = Stmt::Decl(Decl::Var(VarDecl { - span: DUMMY_SP, - kind: VarDeclKind::Var, - declare: false, - decls: v - .vars - .into_iter() - .map(BindingIdent::from) - .map(Pat::Ident) - .map(|name| VarDeclarator { - span: DUMMY_SP, - name, - init: None, - definite: false, - }) - .collect(), - })) - } - - true - } -} - -impl VisitMut for UnreachableHandler { - noop_visit_mut_type!(); - - fn visit_mut_pat(&mut self, n: &mut Pat) { - n.visit_mut_children_with(self); - - if self.in_var_name && self.in_hoisted_var { - if let Pat::Ident(i) = n { - self.vars.push(i.id.clone()); - } - } - } - - fn visit_mut_var_decl(&mut self, n: &mut VarDecl) { - self.in_hoisted_var = n.kind == VarDeclKind::Var; - n.visit_mut_children_with(self); - } - - fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) { - self.in_var_name = true; - n.name.visit_mut_with(self); - self.in_var_name = false; - n.init.visit_mut_with(self); - } - - fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {} - - fn visit_mut_function(&mut self, _: &mut Function) {} -} diff --git a/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs b/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs index 5e16b2b949b..685392e13dc 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/dead_code.rs @@ -2,6 +2,7 @@ use super::Pure; use crate::{ compress::util::{always_terminates, is_fine_for_if_cons}, mode::Mode, + util::ModuleItemExt, }; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; @@ -12,6 +13,51 @@ impl Pure<'_, M> where M: Mode, { + pub(super) fn drop_unreachable_stmts(&mut self, stmts: &mut Vec) + where + T: StmtLike + ModuleItemExt + Take, + { + if !self.options.side_effects { + return; + } + + let idx = stmts + .iter() + .enumerate() + .find(|(_, stmt)| match stmt.as_stmt() { + Some(s) => always_terminates(s), + _ => false, + }); + + if let Some((idx, _)) = idx { + stmts.iter_mut().skip(idx + 1).for_each(|stmt| { + match stmt.as_stmt() { + Some(Stmt::Decl( + Decl::Var(VarDecl { + kind: VarDeclKind::Var, + .. + }) + | Decl::Fn(..), + )) => { + // Preserve + } + + Some(Stmt::Empty(..)) => { + // noop + } + + Some(..) => { + tracing::debug!("Removing unreachable statements"); + self.changed = true; + stmt.take(); + } + + _ => {} + } + }); + } + } + pub(super) fn drop_useless_blocks(&mut self, stmts: &mut Vec) where T: StmtLike, @@ -115,52 +161,4 @@ where *stmts = new; } - - pub(super) fn remove_unreachable_stmts(&mut self, stmts: &mut Vec) - where - T: StmtLike, - { - if !self.options.side_effects { - return; - } - - let mut last = None; - let mut terminated = false; - for (idx, stmt) in stmts.iter().enumerate() { - match stmt.as_stmt() { - Some(stmt) if always_terminates(stmt) => { - terminated = true; - } - _ => { - if terminated { - last = Some(idx); - break; - } - } - } - } - - if let Some(last) = last { - if stmts[last..] - .iter() - .all(|stmt| matches!(stmt.as_stmt(), Some(Stmt::Decl(..)) | None)) - { - return; - } - - self.changed = true; - tracing::debug!("dead_code: Removing unreachable statements"); - - let extras = stmts.drain(last..).collect::>(); - - for extra in extras { - match extra.as_stmt() { - Some(Stmt::Decl(..)) | None => { - stmts.push(extra); - } - _ => {} - } - } - } - } } diff --git a/crates/swc_ecma_minifier/src/compress/pure/drop_console.rs b/crates/swc_ecma_minifier/src/compress/pure/drop_console.rs new file mode 100644 index 00000000000..18636280f38 --- /dev/null +++ b/crates/swc_ecma_minifier/src/compress/pure/drop_console.rs @@ -0,0 +1,52 @@ +use super::Pure; +use swc_common::DUMMY_SP; +use swc_ecma_ast::*; +use swc_ecma_utils::undefined; + +impl Pure<'_, M> { + pub(super) fn drop_console(&mut self, e: &mut Expr) { + if !self.options.drop_console { + return; + } + + if let Expr::Call(CallExpr { callee, .. }) = e { + // Find console.log + let callee = match callee { + Callee::Expr(callee) => callee, + _ => return, + }; + + if let Expr::Member(MemberExpr { + obj: callee_obj, + prop: MemberProp::Ident(_), + .. + }) = &**callee + { + let mut loop_co = &**callee_obj; + loop { + match loop_co { + Expr::Ident(obj) => { + if obj.sym != *"console" { + return; + } + break; + } + + Expr::Member(MemberExpr { + obj: loop_co_obj, + prop: MemberProp::Ident(_), + .. + }) => { + loop_co = loop_co_obj; + } + _ => return, + } + } + + tracing::debug!("drop_console: Removing console call"); + self.changed = true; + *e = *undefined(DUMMY_SP); + } + } + } +} diff --git a/crates/swc_ecma_minifier/src/compress/pure/if_return.rs b/crates/swc_ecma_minifier/src/compress/pure/if_return.rs index e062add5a54..825a168a064 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/if_return.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/if_return.rs @@ -1,43 +1,9 @@ use super::Pure; -use crate::compress::util::{always_terminates, is_fine_for_if_cons, negate}; +use crate::compress::util::{is_fine_for_if_cons, negate}; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; impl Pure<'_, M> { - pub(super) fn drop_unreachable_stmts(&mut self, stmts: &mut Vec) { - if !self.options.dead_code && !self.options.side_effects { - return; - } - - let idx = stmts - .iter() - .enumerate() - .find(|(_, stmt)| always_terminates(stmt)); - - if let Some((idx, _)) = idx { - stmts.iter_mut().skip(idx + 1).for_each(|stmt| match stmt { - Stmt::Decl( - Decl::Var(VarDecl { - kind: VarDeclKind::Var, - .. - }) - | Decl::Fn(..), - ) => { - // Preserve - } - - Stmt::Empty(..) => { - // noop - } - - _ => { - self.changed = true; - stmt.take(); - } - }); - } - } - /// # Input /// /// ```js diff --git a/crates/swc_ecma_minifier/src/compress/pure/mod.rs b/crates/swc_ecma_minifier/src/compress/pure/mod.rs index 768450df873..cff60c88e7a 100644 --- a/crates/swc_ecma_minifier/src/compress/pure/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/pure/mod.rs @@ -18,6 +18,7 @@ mod bools; mod conds; mod ctx; mod dead_code; +mod drop_console; mod evaluate; mod if_return; mod loops; @@ -75,14 +76,14 @@ where { fn handle_stmt_likes(&mut self, stmts: &mut Vec) where - T: ModuleItemExt, + T: ModuleItemExt + Take, Vec: VisitWith + VisitMutWith + VisitMutWith, { self.remove_dead_branch(stmts); - self.remove_unreachable_stmts(stmts); + self.drop_unreachable_stmts(stmts); self.drop_useless_blocks(stmts); @@ -92,8 +93,6 @@ where } fn optimize_fn_stmts(&mut self, stmts: &mut Vec) { - self.drop_unreachable_stmts(stmts); - self.remove_useless_return(stmts); self.negate_if_terminate(stmts, true, false); @@ -222,6 +221,10 @@ where self.remove_invalid(e); + self.drop_console(e); + + self.remove_invalid(e); + if let Expr::Seq(seq) = e { if seq.exprs.is_empty() { *e = Expr::Invalid(Invalid { span: DUMMY_SP }); diff --git a/crates/swc_ecma_minifier/src/compress/util/mod.rs b/crates/swc_ecma_minifier/src/compress/util/mod.rs index 8f4073c3d77..586d1322cc2 100644 --- a/crates/swc_ecma_minifier/src/compress/util/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/util/mod.rs @@ -620,3 +620,95 @@ where }, }); } + +#[derive(Debug, Default)] +pub(super) struct UnreachableHandler { + vars: Vec, + in_var_name: bool, + in_hoisted_var: bool, +} + +impl UnreachableHandler { + /// Assumes `s` is not reachable, and preserves variable declarations and + /// function declarations in `s`. + /// + /// Returns true if statement is changed. + pub fn preserve_vars(s: &mut Stmt) -> bool { + if s.is_empty() { + return false; + } + if let Stmt::Decl(Decl::Var(v)) = s { + let mut changed = false; + for decl in &mut v.decls { + if decl.init.is_some() { + decl.init = None; + changed = true; + } + } + + return changed; + } + + let mut v = Self::default(); + s.visit_mut_with(&mut v); + if v.vars.is_empty() { + *s = Stmt::Empty(EmptyStmt { span: DUMMY_SP }); + } else { + *s = Stmt::Decl(Decl::Var(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + declare: false, + decls: v + .vars + .into_iter() + .map(BindingIdent::from) + .map(Pat::Ident) + .map(|name| VarDeclarator { + span: DUMMY_SP, + name, + init: None, + definite: false, + }) + .collect(), + })) + } + + true + } +} + +impl VisitMut for UnreachableHandler { + noop_visit_mut_type!(); + + fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {} + + fn visit_mut_fn_decl(&mut self, n: &mut FnDecl) { + self.vars.push(n.ident.clone()); + + n.function.visit_mut_with(self); + } + + fn visit_mut_function(&mut self, _: &mut Function) {} + + fn visit_mut_pat(&mut self, n: &mut Pat) { + n.visit_mut_children_with(self); + + if self.in_var_name && self.in_hoisted_var { + if let Pat::Ident(i) = n { + self.vars.push(i.id.clone()); + } + } + } + + fn visit_mut_var_decl(&mut self, n: &mut VarDecl) { + self.in_hoisted_var = n.kind == VarDeclKind::Var; + n.visit_mut_children_with(self); + } + + fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) { + self.in_var_name = true; + n.name.visit_mut_with(self); + self.in_var_name = false; + n.init.visit_mut_with(self); + } +} diff --git a/crates/swc_ecma_minifier/tests/golden.txt b/crates/swc_ecma_minifier/tests/golden.txt index 129b0936cc1..41f2afa0c2d 100644 --- a/crates/swc_ecma_minifier/tests/golden.txt +++ b/crates/swc_ecma_minifier/tests/golden.txt @@ -317,6 +317,8 @@ destructuring/unused_destructuring_object_method_param/input.js directives/class_directives_compression/input.js directives/simple_statement_is_not_a_directive/input.js drop_console/drop_console_1/input.js +drop_console/drop_console_2/input.js +drop_console/unexpected_side_effects_dropping_console/input.js drop_unused/assign_binding/input.js drop_unused/assign_chain/input.js drop_unused/cascade_drop_assign/input.js @@ -737,6 +739,7 @@ labels/labels_5/input.js labels/labels_8/input.js logical_assignment/assign_in_conditional_part_reused/input.js logical_assignment/assignment_in_left_part/input.js +logical_assignment/logical_assignment_not_always_happens/input.js logical_assignment/prematurely_evaluate_assignment/input.js logical_assignment/prematurely_evaluate_assignment_inv/input.js loops/dead_code_condition/input.js diff --git a/crates/swc_ecma_minifier/tests/ignored.txt b/crates/swc_ecma_minifier/tests/ignored.txt index bb54510797d..dd08b2cce14 100644 --- a/crates/swc_ecma_minifier/tests/ignored.txt +++ b/crates/swc_ecma_minifier/tests/ignored.txt @@ -87,8 +87,6 @@ destructuring/issue_t111_2a/input.js destructuring/issue_t111_2b/input.js destructuring/issue_t111_2c/input.js destructuring/issue_t111_3/input.js -drop_console/drop_console_2/input.js -drop_console/unexpected_side_effects_dropping_console/input.js drop_unused/drop_toplevel_keep_assign/input.js drop_unused/global_var/input.js drop_unused/issue_1583/input.js @@ -266,7 +264,6 @@ labels/labels_7/input.js labels/labels_9/input.js logical_assignment/assign_in_conditional_part/input.js logical_assignment/assignment_in_left_part_2/input.js -logical_assignment/logical_assignment_not_always_happens/input.js loops/drop_if_break_3/input.js loops/drop_if_break_4/input.js loops/drop_if_else_break_1/input.js diff --git a/crates/swc_ecma_minifier/tests/terser/compress/drop-console/drop_console_2/output.js b/crates/swc_ecma_minifier/tests/terser/compress/drop-console/drop_console_2/output.js index d48a2162a72..e69de29bb2d 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/drop-console/drop_console_2/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/drop-console/drop_console_2/output.js @@ -1,2 +0,0 @@ -void 0; -void 0;