From 552d9aa344cb6db2dff1e20011411a56f92d4f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 10 Aug 2023 11:48:35 +0900 Subject: [PATCH] fix(es/minifier): Do not drop properties used via `this` (#7785) **Related issue:** - Closes #7783. --- .../fixture/issues-7xxx/7783/input/.swcrc | 71 +++++++++++++++++++ .../tests/fixture/issues-7xxx/7783/input/1.js | 14 ++++ .../fixture/issues-7xxx/7783/output/1.js | 1 + .../thisTypeInFunctions.2.minified.js | 1 + .../src/compress/optimize/unused.rs | 41 ++++++++++- .../tests/fixture/issues/7783/1/input.js | 11 +++ .../tests/fixture/issues/7783/1/output.js | 11 +++ 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc create mode 100644 crates/swc/tests/fixture/issues-7xxx/7783/input/1.js create mode 100644 crates/swc/tests/fixture/issues-7xxx/7783/output/1.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js create mode 100644 crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc new file mode 100644 index 00000000000..55c5f9dd71e --- /dev/null +++ b/crates/swc/tests/fixture/issues-7xxx/7783/input/.swcrc @@ -0,0 +1,71 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": true + }, + "target": "es2015", + "loose": false, + "minify": { + "compress": { + "arguments": false, + "arrows": true, + "booleans": true, + "booleans_as_integers": false, + "collapse_vars": true, + "comparisons": true, + "computed_props": true, + "conditionals": true, + "dead_code": true, + "directives": true, + "drop_console": false, + "drop_debugger": true, + "evaluate": true, + "expression": false, + "hoist_funs": false, + "hoist_props": true, + "hoist_vars": false, + "if_return": true, + "join_vars": true, + "keep_classnames": false, + "keep_fargs": true, + "keep_fnames": false, + "keep_infinity": false, + "loops": true, + "negate_iife": true, + "properties": true, + "reduce_funcs": false, + "reduce_vars": false, + "side_effects": true, + "switches": true, + "typeofs": true, + "unsafe": false, + "unsafe_arrows": false, + "unsafe_comps": false, + "unsafe_Function": false, + "unsafe_math": false, + "unsafe_symbols": false, + "unsafe_methods": false, + "unsafe_proto": false, + "unsafe_regexp": false, + "unsafe_undefined": false, + "unused": true, + "const_to_let": true, + "pristine_globals": true + }, + "mangle": { + "toplevel": false, + "keep_classnames": false, + "keep_fnames": false, + "keep_private_props": false, + "ie8": false, + "safari10": false + } + } + }, + "module": { + "type": "es6" + }, + "minify": true, + "isModule": true +} \ No newline at end of file diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js new file mode 100644 index 00000000000..8fc302ca883 --- /dev/null +++ b/crates/swc/tests/fixture/issues-7xxx/7783/input/1.js @@ -0,0 +1,14 @@ +export default function Home() { + return ( +
{foo.a}
+ ) +} + +const foo = { + get a() { + return `a ${this.b}`; + }, + get b() { + return `b`; + } +} diff --git a/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js new file mode 100644 index 00000000000..58d2bed8bc9 --- /dev/null +++ b/crates/swc/tests/fixture/issues-7xxx/7783/output/1.js @@ -0,0 +1 @@ +export default function e(){return React.createElement("div",null,foo.a)}let foo={get a(){return`a ${this.b}`},get b(){return"b"}}; diff --git a/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js b/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js index 03cbbe791a0..d6663a01356 100644 --- a/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js +++ b/crates/swc/tests/tsc-references/thisTypeInFunctions.2.minified.js @@ -28,6 +28,7 @@ function implicitThis(n) { return this.m + n + 12; } var impl = { + a: 12, explicitVoid2: function() { return _this.a; }, diff --git a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs index 1359603f915..75c5ca591b1 100644 --- a/crates/swc_ecma_minifier/src/compress/optimize/unused.rs +++ b/crates/swc_ecma_minifier/src/compress/optimize/unused.rs @@ -1,8 +1,10 @@ +use rustc_hash::FxHashSet; use swc_atoms::{js_word, JsWord}; use swc_common::{util::take::Take, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access; use swc_ecma_utils::{contains_ident_ref, ExprExt}; +use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; use super::Optimizer; #[cfg(feature = "debug")] @@ -785,11 +787,17 @@ impl Optimizer<'_> { return None; } + let properties_used_via_this = { + let mut v = ThisPropertyVisitor::default(); + obj.visit_with(&mut v); + v.properties + }; + let should_preserve_property = |sym: &JsWord| { if let "toString" = &**sym { return true; } - !usage.accessed_props.contains_key(sym) + !usage.accessed_props.contains_key(sym) && !properties_used_via_this.contains(sym) }; let should_preserve = |key: &PropName| match key { PropName::Ident(k) => should_preserve_property(&k.sym), @@ -824,3 +832,34 @@ impl Optimizer<'_> { None } } + +#[derive(Default)] +struct ThisPropertyVisitor { + properties: FxHashSet, + + should_abort: bool, +} + +impl Visit for ThisPropertyVisitor { + noop_visit_type!(); + + fn visit_member_expr(&mut self, e: &MemberExpr) { + if self.should_abort { + return; + } + + e.visit_children_with(self); + + if let Expr::This(..) = &*e.obj { + match &e.prop { + MemberProp::Ident(p) => { + self.properties.insert(p.sym.clone()); + } + MemberProp::Computed(_) => { + self.should_abort = true; + } + _ => {} + } + } + } +} diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js new file mode 100644 index 00000000000..551c23a1198 --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/input.js @@ -0,0 +1,11 @@ +export default function Home() { + return React.createElement("div", null, foo.a); +} +const foo = { + get a() { + return `a ${this.b}`; + }, + get b() { + return `b`; + } +}; diff --git a/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js new file mode 100644 index 00000000000..4b9c79c6b5d --- /dev/null +++ b/crates/swc_ecma_minifier/tests/fixture/issues/7783/1/output.js @@ -0,0 +1,11 @@ +export default function Home() { + return React.createElement("div", null, foo.a); +} +const foo = { + get a () { + return `a ${this.b}`; + }, + get b () { + return "b"; + } +};