feat(es/minifier): Implement drop_console (#3392)

swc_ecma_minifier:
 - Merge `drop_console` into pure optimizer.
 - Implement `drop_console`. (Closes #2321)
This commit is contained in:
Donny/강동윤 2022-01-28 16:08:41 +09:00 committed by GitHub
parent e215e077ef
commit 91d78000ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 208 additions and 275 deletions

View File

@ -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;
}
}

View File

@ -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 {})
}
)
}

View File

@ -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};

View File

@ -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)));

View File

@ -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<M> Optimizer<'_, M>
@ -553,89 +552,3 @@ where
}
}
}
#[derive(Debug, Default)]
pub(super) struct UnreachableHandler {
vars: Vec<Ident>,
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) {}
}

View File

@ -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<M> Pure<'_, M>
where
M: Mode,
{
pub(super) fn drop_unreachable_stmts<T>(&mut self, stmts: &mut Vec<T>)
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<T>(&mut self, stmts: &mut Vec<T>)
where
T: StmtLike,
@ -115,52 +161,4 @@ where
*stmts = new;
}
pub(super) fn remove_unreachable_stmts<T>(&mut self, stmts: &mut Vec<T>)
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::<Vec<_>>();
for extra in extras {
match extra.as_stmt() {
Some(Stmt::Decl(..)) | None => {
stmts.push(extra);
}
_ => {}
}
}
}
}
}

View File

@ -0,0 +1,52 @@
use super::Pure;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_utils::undefined;
impl<M> 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);
}
}
}
}

View File

@ -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<M> Pure<'_, M> {
pub(super) fn drop_unreachable_stmts(&mut self, stmts: &mut Vec<Stmt>) {
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

View File

@ -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<T>(&mut self, stmts: &mut Vec<T>)
where
T: ModuleItemExt,
T: ModuleItemExt + Take,
Vec<T>: VisitWith<self::vars::VarWithOutInitCounter>
+ VisitMutWith<self::vars::VarPrepender>
+ VisitMutWith<self::vars::VarMover>,
{
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<Stmt>) {
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 });

View File

@ -620,3 +620,95 @@ where
},
});
}
#[derive(Debug, Default)]
pub(super) struct UnreachableHandler {
vars: Vec<Ident>,
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);
}
}

View File

@ -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

View File

@ -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