Skip to content

Commit

Permalink
fix(es/minifier): Fix bugs of the minifier (#2052)
Browse files Browse the repository at this point in the history
swc_ecma_minifier:
 - Test mangler using execution test suite.
 - `mangler`: Preserve `arguments`.
 - `mangler`: Handle shorthand. (#2051)
 - `mangler`: Handle object pattern properties.
 - `precompress`: Don't drop function declarations if the variable with same name is in different scope. (#2011)
  • Loading branch information
kdy1 authored Aug 11, 2021
1 parent a26071f commit a7cb2ab
Show file tree
Hide file tree
Showing 804 changed files with 560 additions and 12 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ecmascript/minifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"]
license = "Apache-2.0/MIT"
name = "swc_ecma_minifier"
repository = "https://github.com/swc-project/swc.git"
version = "0.19.1"
version = "0.19.2"

[features]
debug = []
Expand Down
82 changes: 78 additions & 4 deletions ecmascript/minifier/src/pass/mangle_names/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::option::MangleOptions;
use crate::util::base54::incr_base54;
use fxhash::FxHashMap;
use fxhash::FxHashSet;
use swc_atoms::js_word;
use swc_atoms::JsWord;
use swc_common::SyntaxContext;
use swc_ecma_ast::*;
Expand Down Expand Up @@ -52,21 +53,26 @@ struct Mangler {
}

impl Mangler {
fn rename(&mut self, i: &mut Ident) {
fn rename(&mut self, i: &mut Ident) -> bool {
match i.sym {
js_word!("arguments") => return false,
_ => {}
}

if self.preserved.contains(&i.to_id()) {
return;
return false;
}

if let Some(var) = self.data.as_ref().unwrap().vars.get(&i.to_id()) {
if !var.declared {
return;
return false;
}
}

if let Some(v) = self.renamed.get(&i.to_id()) {
i.span.ctxt = SyntaxContext::empty();
i.sym = v.clone();
return;
return true;
}

loop {
Expand All @@ -83,6 +89,8 @@ impl Mangler {
i.span.ctxt = SyntaxContext::empty();
break;
}

true
}

fn rename_private(&mut self, private_name: &mut PrivateName) {
Expand Down Expand Up @@ -121,6 +129,7 @@ impl VisitMut for Mangler {

self.rename(&mut n.orig);
}

fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);

Expand Down Expand Up @@ -183,6 +192,52 @@ impl VisitMut for Mangler {
n.visit_mut_children_with(self)
}

fn visit_mut_object_pat_prop(&mut self, n: &mut ObjectPatProp) {
match n {
ObjectPatProp::Assign(AssignPatProp {
value: None, key, ..
}) => {
let key_span = key.span.with_ctxt(SyntaxContext::empty());
let orig = key.sym.clone();

if self.rename(key) {
*n = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(Ident::new(orig, key_span)),
value: Box::new(Pat::Ident(key.clone().into())),
});
}
}

ObjectPatProp::Assign(p) => {
let key_span = p.key.span.with_ctxt(SyntaxContext::empty());
let orig = p.key.sym.clone();

if self.rename(&mut p.key) {
if let Some(right) = p.value.take() {
*n = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(Ident::new(orig, key_span)),
value: Box::new(Pat::Assign(AssignPat {
span: p.span,
left: Box::new(Pat::Ident(p.key.clone().into())),
right,
type_ann: None,
})),
});
} else {
*n = ObjectPatProp::KeyValue(KeyValuePatProp {
key: PropName::Ident(Ident::new(orig, key_span)),
value: Box::new(Pat::Ident(p.key.clone().into())),
});
}
}
}

_ => {
n.visit_mut_children_with(self);
}
}
}

fn visit_mut_pat(&mut self, n: &mut Pat) {
n.visit_mut_children_with(self);

Expand All @@ -200,6 +255,25 @@ impl VisitMut for Mangler {
}
}

fn visit_mut_prop(&mut self, n: &mut Prop) {
match n {
Prop::Shorthand(p) => {
let span = p.span.with_ctxt(SyntaxContext::empty());
let orig = p.sym.clone();

if self.rename(p) {
*n = Prop::KeyValue(KeyValueProp {
key: PropName::Ident(Ident::new(orig, span)),
value: Box::new(Expr::Ident(p.clone())),
});
}
}
_ => {
n.visit_mut_children_with(self);
}
}
}

fn visit_mut_script(&mut self, n: &mut Script) {
let data = analyze(&*n, None);
self.data = Some(data);
Expand Down
2 changes: 1 addition & 1 deletion ecmascript/minifier/src/pass/precompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl VisitMut for PrecompressOptimizer<'_> {
if self.options.dead_code || self.options.unused {
if let Some(usage) = self.data.as_ref().unwrap().vars.get(&n.ident.to_id()) {
// Remove if variable with same name exists.
if usage.var_kind.is_some() && usage.var_initialized {
if usage.var_kind.is_some() && usage.var_initialized && usage.is_fn_local {
n.ident.take();
return;
}
Expand Down
2 changes: 1 addition & 1 deletion ecmascript/minifier/tests/compress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ fn projects(input: PathBuf) {
}

/// Tests used to prevent regressions.
#[testing::fixture("tests/compress/exec/**/input.js")]
#[testing::fixture("tests/exec/**/input.js")]
fn base_exec(input: PathBuf) {
let dir = input.parent().unwrap();
let config = dir.join("config.json");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var ClassA = function ClassA() {
"use strict";
_classCallCheck(this, ClassA);
};
module.exports = (function () {
var ClassB = /*#__PURE__*/ function () {
"use strict";
function ClassB() {
_classCallCheck(this, ClassB);
}
_createClass(ClassB, [
{
key: "it",
value: function it() {
this.bb = new ClassB.MyA();
}
}
]);
return ClassB;
}();
_defineProperty(ClassB, "MyA", ClassA);
return ClassB;
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(target, props) {
for(var i = 0; i < props.length; i++){
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || !1, descriptor.configurable = !0, "value" in descriptor && (descriptor.writable = !0), Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
return protoProps && _defineProperties(Constructor.prototype, protoProps), staticProps && _defineProperties(Constructor, staticProps), Constructor;
}
function _defineProperty(obj, key, value) {
return key in obj ? Object.defineProperty(obj, key, {
value: value,
enumerable: !0,
configurable: !0,
writable: !0
}) : obj[key] = value, obj;
}
var ClassA = function ClassA() {
"use strict";
_classCallCheck(this, ClassA);
};
module.exports = (function() {
var ClassB = function() {
"use strict";
function ClassB() {
_classCallCheck(this, ClassB);
}
return _createClass(ClassB, [
{
key: "it",
value: function() {
this.bb = new ClassB.MyA();
}
}
]), ClassB;
}();
return _defineProperty(ClassB, "MyA", ClassA), ClassB;
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class ClassA { }

module.exports = class ClassB {
static MyA = ClassA;

it() {
this.bb = new ClassB.MyA();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class ClassA {
}
module.exports = class ClassB {
static MyA = ClassA;
it() {
this.bb = new ClassB.MyA();
}
};
3 changes: 3 additions & 0 deletions ecmascript/minifier/tests/exec/issues/2011/1/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaults": true
}
24 changes: 24 additions & 0 deletions ecmascript/minifier/tests/exec/issues/2011/1/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class ClassA {
constructor() {
console.log('Class A');
}
}

const cls = class ClassB {
static MyA = ClassA;

constructor() {
console.log('Claas B');
}

it() {
console.log('method it - start');

this.bb = new ClassB.MyA();

console.log('method it - end');
}
}


new cls().it();
3 changes: 3 additions & 0 deletions ecmascript/minifier/tests/exec/issues/2011/2/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"defaults": true
}
Loading

0 comments on commit a7cb2ab

Please sign in to comment.