From 6f25e5774b3ac228b64da47cb370053ed1511b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Mon, 28 Mar 2022 18:14:16 +0900 Subject: [PATCH] feat(es/minifier): Implement more rules for dropping unused assignemnts (#4171) --- .../ES5For-of16_es2015.2.minified.js | 2 +- .../ES5For-of16_es5.2.minified.js | 5 +- ...nmentLHSCanBeAssigned_es2015.2.minified.js | 4 +- ...signmentLHSCanBeAssigned_es5.2.minified.js | 4 +- ...ntLHSCannotBeAssigned_es2015.2.minified.js | 4 +- ...nmentLHSCannotBeAssigned_es5.2.minified.js | 4 +- .../vercel/full/next-31419/2/output/index.js | 13 ++- crates/swc_ecma_minifier/src/analyzer/mod.rs | 3 + .../src/compress/optimize/iife.rs | 7 +- .../src/compress/optimize/mod.rs | 6 +- .../src/compress/optimize/unused.rs | 85 +++++++++++++++++-- .../src/compress/optimize/util.rs | 5 +- crates/swc_ecma_minifier/tests/TODO.txt | 1 - crates/swc_ecma_minifier/tests/golden.txt | 1 + .../drop-unused/issue_2226_1/output.js | 1 - crates/swc_plugin/tests/js.rs | 11 --- crates/wasm/src/lib.rs | 5 +- 17 files changed, 115 insertions(+), 46 deletions(-) delete mode 100644 crates/swc_plugin/tests/js.rs diff --git a/crates/swc/tests/tsc-references/ES5For-of16_es2015.2.minified.js b/crates/swc/tests/tsc-references/ES5For-of16_es2015.2.minified.js index 9844dea506f..1f0c9191f59 100644 --- a/crates/swc/tests/tsc-references/ES5For-of16_es2015.2.minified.js +++ b/crates/swc/tests/tsc-references/ES5For-of16_es2015.2.minified.js @@ -1 +1 @@ -for (let v of [])for (let v1 of [])v1++; +for (let v of [])for (let v1 of []); diff --git a/crates/swc/tests/tsc-references/ES5For-of16_es5.2.minified.js b/crates/swc/tests/tsc-references/ES5For-of16_es5.2.minified.js index 0e2144c5518..fae5ea3c923 100644 --- a/crates/swc/tests/tsc-references/ES5For-of16_es5.2.minified.js +++ b/crates/swc/tests/tsc-references/ES5For-of16_es5.2.minified.js @@ -4,10 +4,7 @@ try { _step.value; var _iteratorNormalCompletion1 = !0, _didIteratorError1 = !1, _iteratorError1 = void 0; try { - for(var _step1, _iterator1 = [][Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = !0){ - var v = _step1.value; - v++; - } + for(var _step1, _iterator1 = [][Symbol.iterator](); !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = !0)_step1.value; } catch (err) { _didIteratorError1 = !0, _iteratorError1 = err; } finally{ diff --git a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es2015.2.minified.js b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es2015.2.minified.js index f70517aaaf0..24ce4994a42 100644 --- a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es2015.2.minified.js +++ b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es2015.2.minified.js @@ -1,4 +1,4 @@ -var E, a, b, x1, x2, x3, x4, x5, x6, x7; +var E, a, b, x1, x2, x3, x4, x6; !function(E) { E[E.a = 0] = "a", E[E.b = 1] = "b"; -}(E || (E = {})), x1 += a, x1 += b, x1 += !0, x1 += 0, x1 += '', x1 += E.a, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += b, x2 += !0, x2 += 0, x2 += '', x2 += E.a, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += 0, x3 += E.a, x3 += null, x3 += void 0, x4 += a, x4 += 0, x4 += E.a, x4 += null, x4 += void 0, x5 += a, x6 += a, x6 += '', x7 += a; +}(E || (E = {})), x1 += a, x1 += b, x1 += !0, x1 += 0, x1 += '', x1 += E.a, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += b, x2 += !0, x2 += 0, x2 += '', x2 += E.a, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += 0, x3 += E.a, x3 += null, x3 += void 0, x4 += a, x4 += 0, x4 += E.a, x4 += null, x4 += void 0, x6 += a, x6 += ''; diff --git a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es5.2.minified.js b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es5.2.minified.js index a7adae21e37..ff51391dd6e 100644 --- a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es5.2.minified.js +++ b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned_es5.2.minified.js @@ -1,4 +1,4 @@ -var E, a, b, x1, x2, x3, x4, x5, x6, x7; +var E, a, b, x1, x2, x3, x4, x6; !function(E) { E[E.a = 0] = "a", E[E.b = 1] = "b"; -}(E || (E = {})), x1 += a, x1 += b, x1 += !0, x1 += 0, x1 += "", x1 += E.a, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += b, x2 += !0, x2 += 0, x2 += "", x2 += E.a, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += 0, x3 += E.a, x3 += null, x3 += void 0, x4 += a, x4 += 0, x4 += E.a, x4 += null, x4 += void 0, x5 += a, x6 += a, x6 += "", x7 += a; +}(E || (E = {})), x1 += a, x1 += b, x1 += !0, x1 += 0, x1 += "", x1 += E.a, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += b, x2 += !0, x2 += 0, x2 += "", x2 += E.a, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += 0, x3 += E.a, x3 += null, x3 += void 0, x4 += a, x4 += 0, x4 += E.a, x4 += null, x4 += void 0, x6 += a, x6 += ""; diff --git a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es2015.2.minified.js b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es2015.2.minified.js index 93050b7e863..1723b644952 100644 --- a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es2015.2.minified.js +++ b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es2015.2.minified.js @@ -1,4 +1,4 @@ -var E, x1, x2, x3, x4, x5; +var E; !function(E) { E[E.a = 0] = "a", E[E.b = 1] = "b", E[E.c = 2] = "c"; -}(E || (E = {})), x1 += '', x2 += '', x3 += '', x4 += '', x5 += ''; +}(E || (E = {})); diff --git a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es5.2.minified.js b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es5.2.minified.js index 25557a26612..1723b644952 100644 --- a/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es5.2.minified.js +++ b/crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCannotBeAssigned_es5.2.minified.js @@ -1,4 +1,4 @@ -var E, x1, x2, x3, x4, x5; +var E; !function(E) { E[E.a = 0] = "a", E[E.b = 1] = "b", E[E.c = 2] = "c"; -}(E || (E = {})), x1 += "", x2 += "", x3 += "", x4 += "", x5 += ""; +}(E || (E = {})); diff --git a/crates/swc/tests/vercel/full/next-31419/2/output/index.js b/crates/swc/tests/vercel/full/next-31419/2/output/index.js index fa9d3a9202c..a1485c80d28 100644 --- a/crates/swc/tests/vercel/full/next-31419/2/output/index.js +++ b/crates/swc/tests/vercel/full/next-31419/2/output/index.js @@ -1,17 +1,14 @@ import * as a from "@swc/helpers"; Promise.all(assignAll).then(function() { - var b = a.asyncToGenerator(function*(e) { - let b = 'DELETE FROM "TABLE" WHERE "UUID" IN ( '; - for(let c in obj){ - let a = obj[c]; - b += `'${a.id}', `; - let d = yield listOfUser(a.id); - d.forEach((b)=>{ + var b = a.asyncToGenerator(function*(c) { + for(let b in obj){ + let a = obj[b]; + `'${a.id}', `, (yield listOfUser(a.id)).forEach((b)=>{ insertQuery += `INSERT INTO "TABLE"("UUID", id, other_ids_here) VALUES ('${uuidv4()}', '${a.id}', now());`; }); } }); - return function(e) { + return function(c) { return b.apply(this, arguments); }; }()); diff --git a/crates/swc_ecma_minifier/src/analyzer/mod.rs b/crates/swc_ecma_minifier/src/analyzer/mod.rs index 0815585d63a..cd346d30c9c 100644 --- a/crates/swc_ecma_minifier/src/analyzer/mod.rs +++ b/crates/swc_ecma_minifier/src/analyzer/mod.rs @@ -76,6 +76,9 @@ pub(crate) struct VarUsageInfo { pub assign_count: usize, pub mutation_by_call_count: usize, + /// ## Things to note + /// + /// - Update is counted as usage pub usage_count: usize, /// The variable itself is modified. diff --git a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs index 4d04036da4a..2186c2ef09c 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/iife.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/iife.rs @@ -1,7 +1,8 @@ use std::{collections::HashMap, mem::swap}; +use rustc_hash::FxHashMap; use swc_atoms::js_word; -use swc_common::{collections::AHashMap, pass::Either, util::take::Take, Spanned, DUMMY_SP}; +use swc_common::{pass::Either, util::take::Take, Spanned, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::{contains_arguments, ident::IdentLike, undefined, ExprFactory, Id}; use swc_ecma_visit::VisitMutWith; @@ -246,8 +247,8 @@ where } } - #[cfg_attr(feature = "debug", tracing::instrument(skip(self, n, vars)))] - pub(super) fn inline_vars_in_node(&mut self, n: &mut N, vars: AHashMap>) + #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + pub(super) fn inline_vars_in_node(&mut self, n: &mut N, vars: FxHashMap>) where N: VisitMutWith, { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs index 682a6052d21..9bdc5ee494d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/mod.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/mod.rs @@ -195,7 +195,7 @@ struct Optimizer<'a, M> { /// Used for inlining. lits: AHashMap>, - vars_for_inlining: AHashMap>, + vars_for_inlining: FxHashMap>, vars_for_prop_hoisting: AHashMap>, /// Used for `hoist_props`. @@ -703,6 +703,10 @@ where self.compress_cond_to_logical_ignoring_return_value(e); + self.drop_unused_update(e); + + self.drop_unused_op_assign(e); + match e { Expr::This(_) | Expr::Invalid(_) | Expr::Lit(..) => { if cfg!(feature = "debug") { diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 2b52c0ef8c2..0c203f2a767 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -508,16 +508,39 @@ where } } + /// This should be only called from ignore_return_value #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] - pub(super) fn drop_unused_assignments(&mut self, e: &mut Expr) { - let assign = match e { - Expr::Assign(e) => e, + pub(super) fn drop_unused_update(&mut self, e: &mut Expr) { + if !self.options.unused { + return; + } + + let update = match e { + Expr::Update(u) => u, _ => return, }; - let has_mark = assign.span.has_mark(self.marks.non_top_level); + if let Expr::Ident(arg) = &*update.arg { + if let Some(var) = self.data.vars.get(&arg.to_id()) { + // Update is counted as usage + if var.declared && var.is_fn_local && var.usage_count == 1 { + self.changed = true; + tracing::debug!( + "unused: Dropping an update '{}{:?}' because it is not used", + arg.sym, + arg.span.ctxt + ); + // This will remove the update. + e.take(); + } + } + } + } - if !has_mark && !self.options.unused { + /// This should be only called from ignore_return_value + #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + pub(super) fn drop_unused_op_assign(&mut self, e: &mut Expr) { + if !self.options.unused { return; } @@ -529,6 +552,58 @@ where return; } + let assign = match e { + Expr::Assign(AssignExpr { op: op!("="), .. }) => return, + // RHS may not be evaluated + Expr::Assign(AssignExpr { + op: op!("&&=") | op!("||=") | op!("&&="), + .. + }) => return, + Expr::Assign(e) => e, + _ => return, + }; + assign.left.map_with_mut(|left| left.normalize_ident()); + + if let PatOrExpr::Pat(p) = &assign.left { + if let Pat::Ident(left) = &**p { + if let Some(var) = self.data.vars.get(&left.to_id()) { + // TODO: We don't need fn_local check + if var.declared && var.is_fn_local && var.usage_count == 1 { + self.changed = true; + tracing::debug!( + "unused: Dropping an op-assign '{}{:?}' because it is not used", + left.id.sym, + left.id.span.ctxt + ); + // This will remove the op-assign. + *e = *assign.right.take(); + } + } + } + } + } + + #[cfg_attr(feature = "debug", tracing::instrument(skip_all))] + pub(super) fn drop_unused_assignments(&mut self, e: &mut Expr) { + if self.ctx.is_delete_arg { + return; + } + + if self.data.top.has_eval_call || self.data.top.has_with_stmt { + return; + } + + let assign = match e { + Expr::Assign(e) => e, + _ => return, + }; + + let has_mark = assign.span.has_mark(self.marks.non_top_level); + + if !has_mark && !self.options.unused { + return; + } + let used_arguments = self .data .scopes diff --git a/crates/swc_ecma_minifier/src/compress/optimize/util.rs b/crates/swc_ecma_minifier/src/compress/optimize/util.rs index 9d8bfc1fbd3..85dd3d3009d 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/util.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/util.rs @@ -1,7 +1,8 @@ use std::ops::{Deref, DerefMut}; +use rustc_hash::FxHashMap; use swc_atoms::JsWord; -use swc_common::{collections::AHashMap, Span}; +use swc_common::Span; use swc_ecma_ast::*; use swc_ecma_utils::{ident::IdentLike, prop_name_eq, ExprExt, Id}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; @@ -168,7 +169,7 @@ pub(crate) fn is_valid_for_lhs(e: &Expr) -> bool { } pub(crate) struct MultiReplacer { - pub vars: AHashMap>, + pub vars: FxHashMap>, pub changed: bool, } diff --git a/crates/swc_ecma_minifier/tests/TODO.txt b/crates/swc_ecma_minifier/tests/TODO.txt index 1f99c51f2b8..6c2fa7df226 100644 --- a/crates/swc_ecma_minifier/tests/TODO.txt +++ b/crates/swc_ecma_minifier/tests/TODO.txt @@ -174,7 +174,6 @@ drop_unused/issue_2105_2/input.js drop_unused/issue_2136_2/input.js drop_unused/issue_2136_3/input.js drop_unused/issue_2163/input.js -drop_unused/issue_2226_1/input.js drop_unused/issue_2226_2/input.js drop_unused/issue_2226_3/input.js drop_unused/issue_2288/input.js diff --git a/crates/swc_ecma_minifier/tests/golden.txt b/crates/swc_ecma_minifier/tests/golden.txt index 074beb1e93c..f4d26cf16ee 100644 --- a/crates/swc_ecma_minifier/tests/golden.txt +++ b/crates/swc_ecma_minifier/tests/golden.txt @@ -381,6 +381,7 @@ drop_unused/issue_1656/input.js drop_unused/issue_1715_1/input.js drop_unused/issue_1715_2/input.js drop_unused/issue_2136_1/input.js +drop_unused/issue_2226_1/input.js drop_unused/issue_2995/input.js drop_unused/issue_3146_1/input.js drop_unused/issue_3146_2/input.js diff --git a/crates/swc_ecma_minifier/tests/terser/compress/drop-unused/issue_2226_1/output.js b/crates/swc_ecma_minifier/tests/terser/compress/drop-unused/issue_2226_1/output.js index f26a924a1ea..7e7704c9b74 100644 --- a/crates/swc_ecma_minifier/tests/terser/compress/drop-unused/issue_2226_1/output.js +++ b/crates/swc_ecma_minifier/tests/terser/compress/drop-unused/issue_2226_1/output.js @@ -6,7 +6,6 @@ function f2(a) { b; } function f3(a) { - 0; } function f4() { var a = b; diff --git a/crates/swc_plugin/tests/js.rs b/crates/swc_plugin/tests/js.rs deleted file mode 100644 index 0092c4699b2..00000000000 --- a/crates/swc_plugin/tests/js.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Ensure that wrong macro definitions are caught by swc monorepo. - -use swc_ecma_visit::Fold; - -fn drop_console(_: ()) -> impl Fold { - DropConsole -} - -struct DropConsole; - -impl Fold for DropConsole {} diff --git a/crates/wasm/src/lib.rs b/crates/wasm/src/lib.rs index e0260422348..6d22e3a4eff 100644 --- a/crates/wasm/src/lib.rs +++ b/crates/wasm/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unused_unit)] + use std::sync::Arc; use anyhow::{Context, Error}; @@ -8,7 +10,7 @@ use swc::{ }; use swc_common::{comments::Comments, FileName, FilePathMapping, SourceMap}; use swc_ecmascript::ast::{EsVersion, Program}; -use wasm_bindgen::{prelude::*, JsCast}; +use wasm_bindgen::prelude::*; fn convert_err(err: Error) -> JsValue { format!("{:?}", err).into() @@ -156,6 +158,7 @@ pub fn transform_sync( { if experimental_plugin_bytes_resolver.is_object() { use js_sys::{Array, Object, Uint8Array}; + use wasm_bindgen::JsCast; // TODO: This is probably very inefficient, including each transform // deserializes plugin bytes.