aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/compress.js71
-rw-r--r--lib/scope.js2
-rw-r--r--test/compress/collapse_vars.js420
3 files changed, 481 insertions, 12 deletions
diff --git a/lib/compress.js b/lib/compress.js
index fdf3a2b6..54ac3d75 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -796,8 +796,8 @@ merge(Compressor.prototype, {
});
function drop_decl(def) {
- def._eliminiated = (def._eliminiated || 0) + 1;
- if (def.orig.length == def._eliminiated) {
+ def.eliminated++;
+ if (def.orig.length == def.eliminated) {
def.scope.functions.del(def.name);
def.scope.variables.del(def.name);
}
@@ -854,10 +854,14 @@ merge(Compressor.prototype, {
// Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
- var one_off = lhs instanceof AST_Symbol && lhs.definition().references.length == 1;
+ var replace_all = candidate.multiple;
+ if (!replace_all && lhs instanceof AST_SymbolRef) {
+ var def = lhs.definition();
+ replace_all = def.references.length - def.replaced == 1;
+ }
var side_effects = value_has_side_effects(candidate);
var hit = candidate.name instanceof AST_SymbolFunarg;
- var abort = false, replaced = false, can_replace = !args || !hit;
+ var abort = false, replaced = 0, can_replace = !args || !hit;
var tt = new TreeTransformer(function(node, descend) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
@@ -886,7 +890,8 @@ merge(Compressor.prototype, {
&& !(node instanceof AST_SymbolDeclaration)
&& !is_lhs(node, parent)
&& lhs.equivalent_to(node)) {
- CHANGED = replaced = abort = true;
+ CHANGED = abort = true;
+ replaced++;
compressor.info("Collapsing {name} [{file}:{line},{col}]", {
name: node.print_to_string(),
file: node.start.file,
@@ -897,8 +902,13 @@ merge(Compressor.prototype, {
return make_node(AST_UnaryPrefix, candidate, candidate);
}
if (candidate instanceof AST_VarDef) {
+ if (candidate.multiple) {
+ abort = false;
+ return node;
+ }
var def = candidate.name.definition();
- if (def.references.length == 1 && !compressor.exposed(def)) {
+ if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) {
+ def.replaced++;
return maintain_this_binding(parent, node, candidate.value);
}
return make_node(AST_Assign, candidate, {
@@ -922,7 +932,7 @@ merge(Compressor.prototype, {
|| side_effects && !references_in_scope(node.definition()))
|| (sym = lhs_or_def(node))
&& (sym instanceof AST_PropAccess || sym.name in lvalues)
- || (side_effects || !one_off)
+ || (side_effects || !replace_all)
&& (parent instanceof AST_Binary && lazy_op(parent.operator)
|| parent instanceof AST_Case
|| parent instanceof AST_Conditional
@@ -935,7 +945,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Default || node instanceof AST_Scope) return node;
});
if (!can_replace) {
- for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; j < args.length; j++) {
+ for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
args[j].transform(tt);
}
can_replace = true;
@@ -943,6 +953,33 @@ merge(Compressor.prototype, {
for (var i = stat_index; !abort && i < statements.length; i++) {
statements[i].transform(tt);
}
+ if (candidate.multiple) {
+ var def = candidate.name.definition();
+ if (abort && def.references.length > replaced) replaced = false;
+ else {
+ abort = false;
+ hit = candidate.name instanceof AST_SymbolFunarg;
+ var value_def = candidate.value.definition();
+ for (var i = stat_index; !abort && i < statements.length; i++) {
+ statements[i].transform(new TreeTransformer(function(node) {
+ if (abort) return node;
+ if (!hit) {
+ if (node === candidate) {
+ hit = true;
+ return node;
+ }
+ return;
+ }
+ if (node instanceof AST_SymbolRef && node.name == def.name) {
+ def.replaced++;
+ value_def.replaced--;
+ if (!--replaced) abort = true;
+ return candidate.value;
+ }
+ }));
+ }
+ }
+ }
if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1);
}
}
@@ -956,7 +993,7 @@ merge(Compressor.prototype, {
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var fn_strict = compressor.has_directive("use strict");
- if (fn_strict && fn.body.indexOf(fn_strict) < 0) fn_strict = false;
+ if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
var len = fn.argnames.length;
args = iife.args.slice(len);
var names = Object.create(null);
@@ -1012,12 +1049,22 @@ merge(Compressor.prototype, {
}
}
+ function mangleable_var(expr) {
+ var value = expr.value;
+ if (!(value instanceof AST_SymbolRef)) return false;
+ if (value.name == "arguments") return false;
+ if (value.definition().undeclared) return false;
+ expr.multiple = true;
+ return true;
+ }
+
function get_lhs(expr) {
if (expr instanceof AST_VarDef) {
var def = expr.name.definition();
- if (def.orig.length - (def._eliminiated || 0) > 1
- && !(expr.name instanceof AST_SymbolFunarg)
- || def.references.length == 1 && !compressor.exposed(def)) {
+ var declared = def.orig.length - def.eliminated;
+ var referenced = def.references.length - def.replaced;
+ if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)
+ || (referenced > 1 ? mangleable_var(expr) : !compressor.exposed(def))) {
return make_node(AST_SymbolRef, expr.name, expr.name);
}
} else {
diff --git a/lib/scope.js b/lib/scope.js
index 8e766a56..0d2a7aeb 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -46,8 +46,10 @@
function SymbolDef(scope, index, orig) {
this.name = orig.name;
this.orig = [ orig ];
+ this.eliminated = 0;
this.scope = scope;
this.references = [];
+ this.replaced = 0;
this.global = false;
this.mangled_name = null;
this.undeclared = false;
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index d98dca95..402bd22b 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -3098,3 +3098,423 @@ issue_2437: {
}();
}
}
+
+issue_2436_1: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log({
+ x: o.a,
+ y: o.b,
+ });
+ }
+ expect_stdout: true
+}
+
+issue_2436_2: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ o.a = 3;
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ o.a = 3;
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect_stdout: true
+}
+
+issue_2436_3: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ o = {
+ a: 3,
+ b: 4,
+ };
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ o = {
+ a: 3,
+ b: 4,
+ };
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect_stdout: true
+}
+
+issue_2436_4: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ var o;
+ }(o));
+ }
+ expect: {
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }({
+ a: 1,
+ b: 2,
+ }));
+ }
+ expect_stdout: true
+}
+
+issue_2436_5: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(o) {
+ return {
+ x: o.a,
+ y: o.b,
+ };
+ }(o));
+ }
+ expect: {
+ console.log(function(o) {
+ return {
+ x: o.a,
+ y: o.b,
+ };
+ }({
+ a: 1,
+ b: 2,
+ }));
+ }
+ expect_stdout: true
+}
+
+issue_2436_6: {
+ options = {
+ collapse_vars: true,
+ evaluate: true,
+ inline: true,
+ passes: 2,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ unsafe: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ console.log({
+ x: 1,
+ y: 2,
+ });
+ }
+ expect_stdout: true
+}
+
+issue_2436_7: {
+ options = {
+ collapse_vars: true,
+ hoist_props: true,
+ inline: true,
+ passes: 3,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ console.log({
+ x: 1,
+ y: 2,
+ });
+ }
+ expect_stdout: true
+}
+
+issue_2436_8: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect_stdout: true
+}
+
+issue_2436_9: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: "strict",
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = console;
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect: {
+ var o = console;
+ console.log(function(c) {
+ return {
+ x: c.a,
+ y: c.b,
+ };
+ }(o));
+ }
+ expect_stdout: true
+}
+
+issue_2436_10: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ pure_getters: true,
+ reduce_vars: true,
+ toplevel: true,
+ unused: true,
+ }
+ input: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ function f(n) {
+ o = { b: 3 };
+ return n;
+ }
+ console.log(function(c) {
+ return [
+ c.a,
+ f(c.b),
+ c.b,
+ ];
+ }(o).join(" "));
+ }
+ expect: {
+ var o = {
+ a: 1,
+ b: 2,
+ };
+ function f(n) {
+ o = { b: 3 };
+ return n;
+ }
+ console.log(function(c) {
+ return [
+ c.a,
+ f(c.b),
+ c.b,
+ ];
+ }(o).join(" "));
+ }
+ expect_stdout: "1 2 2"
+}
+
+issue_2436_11: {
+ options = {
+ collapse_vars: true,
+ join_vars: true,
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ function matrix() {}
+ function isCollection() {}
+ function _randomDataForMatrix() {}
+ function _randomInt() {}
+ function f(arg1, arg2) {
+ if (isCollection(arg1)) {
+ var size = arg1;
+ var max = arg2;
+ var min = 0;
+ var res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
+ return size && true === size.isMatrix ? matrix(res) : res;
+ } else {
+ var min = arg1;
+ var max = arg2;
+ return _randomInt(min, max);
+ }
+ }
+ }
+ expect: {
+ function matrix() {}
+ function isCollection() {}
+ function _randomDataForMatrix() {}
+ function _randomInt() {}
+ function f(arg1, arg2) {
+ if (isCollection(arg1)) {
+ var size = arg1, max = arg2, min = 0, res = _randomDataForMatrix(size.valueOf(), min, max, _randomInt);
+ return size && true === size.isMatrix ? matrix(res) : res;
+ } else {
+ return _randomInt(min = arg1, max = arg2);
+ }
+ }
+ }
+}
+
+issue_2436_12: {
+ options = {
+ collapse_vars: true,
+ unused: true,
+ }
+ input: {
+ function isUndefined() {}
+ function f() {
+ var viewValue = this.$$lastCommittedViewValue;
+ var modelValue = viewValue;
+ return isUndefined(modelValue) ? modelValue : null;
+ }
+ }
+ expect: {
+ function isUndefined() {}
+ function f() {
+ var modelValue = this.$$lastCommittedViewValue;
+ return isUndefined(modelValue) ? modelValue : null;
+ }
+ }
+}