diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index c5343f3f..60b77c7c 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -15,6 +15,8 @@ UglifyJS alone - without third party tools or libraries. Ideally the input should be as small as possible. Post a link to a gist if necessary. + + Issues without a reproducible test case will be closed. --> **The `uglifyjs` CLI command executed or `minify()` options used.** diff --git a/README.md b/README.md index bc8a90bf..9c953471 100644 --- a/README.md +++ b/README.md @@ -706,11 +706,12 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u Specify `"strict"` to treat `foo.bar` as side-effect-free only when `foo` is certain to not throw, i.e. not `null` or `undefined`. -- `reduce_funcs` (default: `true`) -- Allows single-use functions - to be inlined as function expressions when permissible. - Enabled by default. Option depends on `reduce_vars` being enabled. - For speed critical code this option should be disabled. - +- `reduce_funcs` (default: `true`) -- Allows single-use functions to be + inlined as function expressions when permissible allowing further + optimization. Enabled by default. Option depends on `reduce_vars` + being enabled. Some code runs faster in the Chrome V8 engine if this + option is disabled. Does not negatively impact other major browsers. + - `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and used as constant values. @@ -907,6 +908,9 @@ can pass additional arguments that control the code output: - `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts) +- `webkit` (default `false`) -- enable workarounds for WebKit bugs. + PhantomJS users should set this option to `true`. + - `width` (default `80`) -- only takes effect when beautification is on, this specifies an (orientative) line width that the beautifier will try to obey. It refers to the width of the line text (excluding indentation). diff --git a/bin/uglifyjs b/bin/uglifyjs index 9c13941e..1b01aa17 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -15,7 +15,7 @@ var path = require("path"); var program = require("commander"); var UglifyJS = require("../tools/node"); -var skip_keys = [ "cname", "enclosed", "parent_scope", "scope", "thedef", "uses_eval", "uses_with" ]; +var skip_keys = [ "cname", "enclosed", "inlined", "parent_scope", "scope", "thedef", "uses_eval", "uses_with" ]; var files = {}; var options = { compress: false, diff --git a/lib/ast.js b/lib/ast.js index 6ef203dc..01fd54b2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -389,15 +389,15 @@ var AST_Accessor = DEFNODE("Accessor", null, { $documentation: "A setter/getter function. The `name` property is always null." }, AST_Lambda); -var AST_Function = DEFNODE("Function", null, { +var AST_Function = DEFNODE("Function", "inlined", { $documentation: "A function expression" }, AST_Lambda); -var AST_Arrow = DEFNODE("Arrow", null, { +var AST_Arrow = DEFNODE("Arrow", "inlined", { $documentation: "An ES6 Arrow function ((a) => b)" }, AST_Lambda); -var AST_Defun = DEFNODE("Defun", null, { +var AST_Defun = DEFNODE("Defun", "inlined", { $documentation: "A function definition" }, AST_Lambda); diff --git a/lib/compress.js b/lib/compress.js index 7d1bd889..aa97b13c 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -386,6 +386,7 @@ merge(Compressor.prototype, { } } if (node instanceof AST_Defun) { + node.inlined = false; var d = node.name.definition(); if (compressor.exposed(d) || safe_to_read(d)) { d.fixed = false; @@ -402,6 +403,7 @@ merge(Compressor.prototype, { return true; } if (is_func_expr(node)) { + node.inlined = false; push(); var iife; if (!node.name @@ -835,8 +837,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); } @@ -878,6 +880,115 @@ merge(Compressor.prototype, { var args; var candidates = []; var stat_index = statements.length; + var scanner = new TreeTransformer(function(node, descend) { + if (abort) return node; + // Skip nodes before `candidate` as quickly as possible + if (!hit) { + if (node === candidate) { + hit = true; + return node; + } + return; + } + // Stop immediately if these node types are encountered + var parent = scanner.parent(); + if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left) + || node instanceof AST_Await + || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) + || node instanceof AST_Debugger + || node instanceof AST_Destructuring + || node instanceof AST_IterationStatement && !(node instanceof AST_For) + || node instanceof AST_SymbolRef && !node.is_declared(compressor) + || node instanceof AST_Try + || node instanceof AST_With + || parent instanceof AST_For && node !== parent.init) { + abort = true; + return node; + } + // Replace variable with assignment when found + if (can_replace + && !(node instanceof AST_SymbolDeclaration) + && lhs.equivalent_to(node)) { + if (is_lhs(node, parent)) { + if (candidate.multiple) replaced++; + return node; + } + CHANGED = abort = true; + replaced++; + compressor.info("Collapsing {name} [{file}:{line},{col}]", { + name: node.print_to_string(), + file: node.start.file, + line: node.start.line, + col: node.start.col + }); + if (candidate instanceof AST_UnaryPostfix) { + 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 - def.replaced == 1 && !compressor.exposed(def)) { + def.replaced++; + return maintain_this_binding(parent, node, candidate.value); + } + return make_node(AST_Assign, candidate, { + operator: "=", + left: make_node(AST_SymbolRef, candidate.name, candidate.name), + right: candidate.value + }); + } + candidate.write_only = false; + return candidate; + } + // These node types have child nodes that execute sequentially, + // but are otherwise not safe to scan into or beyond them. + var sym; + if (node instanceof AST_Call + || node instanceof AST_Exit + || node instanceof AST_PropAccess + && (side_effects || node.expression.may_throw_on_access(compressor)) + || node instanceof AST_SymbolRef + && (lvalues[node.name] + || side_effects && !references_in_scope(node.definition())) + || (sym = lhs_or_def(node)) + && (sym instanceof AST_PropAccess || sym.name in lvalues) + || (side_effects || !replace_all) + && (parent instanceof AST_Binary && lazy_op(parent.operator) + || parent instanceof AST_Case + || parent instanceof AST_Conditional + || parent instanceof AST_If)) { + if (!(node instanceof AST_Scope)) descend(node, scanner); + abort = true; + return node; + } + // Skip (non-executed) functions and (leading) default case in switch statements + if (node instanceof AST_Default || node instanceof AST_Scope) return node; + }); + var multi_replacer = new TreeTransformer(function(node) { + if (abort) return node; + // Skip nodes before `candidate` as quickly as possible + if (!hit) { + if (node === candidate) { + hit = true; + return node; + } + return; + } + // Replace variable when found + if (node instanceof AST_SymbolRef + && node.name == def.name) { + if (!--replaced) abort = true; + if (is_lhs(node, multi_replacer.parent())) return node; + def.replaced++; + value_def.replaced--; + return candidate.value; + } + // Skip (non-executed) functions and (leading) default case in switch statements + if (node instanceof AST_Default || node instanceof AST_Scope) return node; + }); while (--stat_index >= 0) { // Treat parameters as collapsible in IIFE, i.e. // function(a, b){ ... }(x()); @@ -893,96 +1004,34 @@ 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 tt = new TreeTransformer(function(node, descend) { - if (abort) return node; - // Skip nodes before `candidate` as quickly as possible - if (!hit) { - if (node === candidate) { - hit = true; - return node; - } - return; - } - // Stop immediately if these node types are encountered - var parent = tt.parent(); - if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left) - || node instanceof AST_Await - || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression) - || node instanceof AST_Debugger - || node instanceof AST_Destructuring - || node instanceof AST_IterationStatement && !(node instanceof AST_For) - || node instanceof AST_SymbolRef && !node.is_declared(compressor) - || node instanceof AST_Try - || node instanceof AST_With - || parent instanceof AST_For && node !== parent.init) { - abort = true; - return node; - } - // Replace variable with assignment when found - if (can_replace - && !(node instanceof AST_SymbolDeclaration) - && !is_lhs(node, parent) - && lhs.equivalent_to(node)) { - CHANGED = replaced = abort = true; - compressor.info("Collapsing {name} [{file}:{line},{col}]", { - name: node.print_to_string(), - file: node.start.file, - line: node.start.line, - col: node.start.col - }); - if (candidate instanceof AST_UnaryPostfix) { - return make_node(AST_UnaryPrefix, candidate, candidate); - } - if (candidate instanceof AST_VarDef) { - var def = candidate.name.definition(); - if (def.references.length == 1 && !compressor.exposed(def)) { - return maintain_this_binding(parent, node, candidate.value); - } - return make_node(AST_Assign, candidate, { - operator: "=", - left: make_node(AST_SymbolRef, candidate.name, candidate.name), - right: candidate.value - }); - } - candidate.write_only = false; - return candidate; - } - // These node types have child nodes that execute sequentially, - // but are otherwise not safe to scan into or beyond them. - var sym; - if (node instanceof AST_Call - || node instanceof AST_Exit - || node instanceof AST_PropAccess - && (side_effects || node.expression.may_throw_on_access(compressor)) - || node instanceof AST_SymbolRef - && (lvalues[node.name] - || side_effects && !references_in_scope(node.definition())) - || (sym = lhs_or_def(node)) - && (sym instanceof AST_PropAccess || sym.name in lvalues) - || (side_effects || !one_off) - && (parent instanceof AST_Binary && lazy_op(parent.operator) - || parent instanceof AST_Case - || parent instanceof AST_Conditional - || parent instanceof AST_If)) { - if (!(node instanceof AST_Scope)) descend(node, tt); - abort = true; - return node; - } - // Skip (non-executed) functions and (leading) default case in switch statements - if (node instanceof AST_Default || node instanceof AST_Scope) return node; - }); + var abort = false, replaced = 0, can_replace = !args || !hit; if (!can_replace) { - for (var j = compressor.self().argnames.lastIndexOf(candidate.__name || candidate.name) + 1; j < args.length; j++) { - args[j].transform(tt); + for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { + args[j].transform(scanner); } can_replace = true; } for (var i = stat_index; !abort && i < statements.length; i++) { - statements[i].transform(tt); + statements[i].transform(scanner); + } + if (candidate.multiple) { + var def = candidate.name.definition(); + if (abort && def.references.length - def.replaced > 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(multi_replacer); + } + } } if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); } @@ -1025,7 +1074,7 @@ merge(Compressor.prototype, { return !(arg instanceof AST_Expansion); })) { 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); @@ -1081,12 +1130,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 && expr.name instanceof AST_SymbolDeclaration) { 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 { @@ -2655,12 +2714,14 @@ merge(Compressor.prototype, { var def = tail.pop(); compressor.warn("Converting duplicated definition of variable {name} to assignment [{file}:{line},{col}]", template(def.name)); remove(var_defs, def); - drop_decl(def.name.definition()); side_effects.unshift(make_node(AST_Assign, def, { operator: "=", left: make_node(AST_SymbolRef, def.name, def.name), right: def.value })); + def = def.name.definition(); + drop_decl(def); + def.replaced--; } } if (head.length > 0 || tail.length > 0) { @@ -2885,17 +2946,29 @@ merge(Compressor.prototype, { return self; }); + AST_Scope.DEFMETHOD("make_var_name", function(prefix) { + var var_names = this.var_names; + if (!var_names) { + this.var_names = var_names = Object.create(null); + this.enclosed.forEach(function(def) { + var_names[def.name] = true; + }); + this.variables.each(function(def, name) { + var_names[name] = true; + }); + } + prefix = prefix.replace(/[^a-z_$]+/ig, "_"); + var name = prefix; + for (var i = 0; var_names[name]; i++) name = prefix + "$" + i; + var_names[name] = true; + return name; + }); + AST_Scope.DEFMETHOD("hoist_properties", function(compressor){ var self = this; if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self; + var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false; var defs_by_id = Object.create(null); - var var_names = Object.create(null); - self.enclosed.forEach(function(def) { - var_names[def.name] = true; - }); - self.variables.each(function(def, name) { - var_names[name] = true; - }); var tt = new TreeTransformer(function(node) { if (node instanceof AST_Definitions && tt.parent() instanceof AST_Export) return node; if (node instanceof AST_VarDef) { @@ -2904,6 +2977,7 @@ merge(Compressor.prototype, { && !(def = sym.definition()).escaped && !def.single_use && !def.direct_access + && !top_retain(def) && (value = sym.fixed_value()) === node.value && value instanceof AST_Object) { var defs = new Dictionary(); @@ -2935,17 +3009,13 @@ merge(Compressor.prototype, { } function make_sym(key) { - var prefix = sym.name + "_" + key.toString().replace(/[^a-z_$]+/ig, "_"); - var name = prefix; - for (var i = 0; var_names[name]; i++) name = prefix + "$" + i; var new_var = make_node(sym.CTOR, sym, { - name: name, + name: self.make_var_name(sym.name + "_" + key), scope: self }); var def = self.def_variable(new_var); defs.set(key, def); self.enclosed.push(def); - var_names[name] = true; return new_var; } }); @@ -3569,136 +3639,139 @@ merge(Compressor.prototype, { self.args.length = last; } if (compressor.option("unsafe")) { - if (is_undeclared_ref(exp)) { - switch (exp.name) { - case "Array": - if (self.args.length != 1) { - return make_node(AST_Array, self, { - elements: self.args - }).optimize(compressor); - } - break; - case "Object": - if (self.args.length == 0) { - return make_node(AST_Object, self, { - properties: [] - }); - } - break; - case "String": - if (self.args.length == 0) return make_node(AST_String, self, { - value: "" - }); - if (self.args.length <= 1) return make_node(AST_Binary, self, { - left: self.args[0], - operator: "+", - right: make_node(AST_String, self, { value: "" }) + if (is_undeclared_ref(exp)) switch (exp.name) { + case "Array": + if (self.args.length != 1) { + return make_node(AST_Array, self, { + elements: self.args }).optimize(compressor); - break; - case "Number": - if (self.args.length == 0) return make_node(AST_Number, self, { - value: 0 - }); - if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { - expression: self.args[0], - operator: "+" - }).optimize(compressor); - case "Boolean": - if (self.args.length == 0) return make_node(AST_False, self); - if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { - expression: make_node(AST_UnaryPrefix, self, { - expression: self.args[0], - operator: "!" - }), - operator: "!" - }).optimize(compressor); - break; - case "Symbol": - // Symbol's argument is only used for debugging. - self.args = []; - return self; } - } - else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) { - return make_node(AST_Binary, self, { + break; + case "Object": + if (self.args.length == 0) { + return make_node(AST_Object, self, { + properties: [] + }); + } + break; + case "String": + if (self.args.length == 0) return make_node(AST_String, self, { + value: "" + }); + if (self.args.length <= 1) return make_node(AST_Binary, self, { + left: self.args[0], + operator: "+", + right: make_node(AST_String, self, { value: "" }) + }).optimize(compressor); + break; + case "Number": + if (self.args.length == 0) return make_node(AST_Number, self, { + value: 0 + }); + if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { + expression: self.args[0], + operator: "+" + }).optimize(compressor); + case "Boolean": + if (self.args.length == 0) return make_node(AST_False, self); + if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, { + expression: make_node(AST_UnaryPrefix, self, { + expression: self.args[0], + operator: "!" + }), + operator: "!" + }).optimize(compressor); + break; + case "Symbol": + // Symbol's argument is only used for debugging. + self.args = []; + return self; + } else if (exp instanceof AST_Dot) switch(exp.property) { + case "toString": + if (self.args.length == 0) return make_node(AST_Binary, self, { left: make_node(AST_String, self, { value: "" }), operator: "+", right: exp.expression }).optimize(compressor); - } - else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: { - var separator; - if (self.args.length > 0) { - separator = self.args[0].evaluate(compressor); - if (separator === self.args[0]) break EXIT; // not a constant - } - var elements = []; - var consts = []; - for (var i = 0, len = exp.expression.elements.length; i < len; i++) { - var el = exp.expression.elements[i]; - if (el instanceof AST_Expansion) break EXIT; - var value = el.evaluate(compressor); - if (value !== el) { - consts.push(value); - } else { - if (consts.length > 0) { - elements.push(make_node(AST_String, self, { - value: consts.join(separator) - })); - consts.length = 0; + break; + case "join": + if (exp.expression instanceof AST_Array) EXIT: { + var separator; + if (self.args.length > 0) { + separator = self.args[0].evaluate(compressor); + if (separator === self.args[0]) break EXIT; // not a constant + } + var elements = []; + var consts = []; + for (var i = 0, len = exp.expression.elements.length; i < len; i++) { + var el = exp.expression.elements[i]; + if (el instanceof AST_Expansion) break EXIT; + var value = el.evaluate(compressor); + if (value !== el) { + consts.push(value); + } else { + if (consts.length > 0) { + elements.push(make_node(AST_String, self, { + value: consts.join(separator) + })); + consts.length = 0; + } + elements.push(el); } - elements.push(el); } - } - if (consts.length > 0) { - elements.push(make_node(AST_String, self, { - value: consts.join(separator) - })); - } - if (elements.length == 0) return make_node(AST_String, self, { value: "" }); - if (elements.length == 1) { - if (elements[0].is_string(compressor)) { - return elements[0]; + if (consts.length > 0) { + elements.push(make_node(AST_String, self, { + value: consts.join(separator) + })); } - return make_node(AST_Binary, elements[0], { - operator : "+", - left : make_node(AST_String, self, { value: "" }), - right : elements[0] - }); - } - if (separator == "") { - var first; - if (elements[0].is_string(compressor) - || elements[1].is_string(compressor)) { - first = elements.shift(); - } else { - first = make_node(AST_String, self, { value: "" }); - } - return elements.reduce(function(prev, el){ - return make_node(AST_Binary, el, { + if (elements.length == 0) return make_node(AST_String, self, { value: "" }); + if (elements.length == 1) { + if (elements[0].is_string(compressor)) { + return elements[0]; + } + return make_node(AST_Binary, elements[0], { operator : "+", - left : prev, - right : el + left : make_node(AST_String, self, { value: "" }), + right : elements[0] }); - }, first).optimize(compressor); + } + if (separator == "") { + var first; + if (elements[0].is_string(compressor) + || elements[1].is_string(compressor)) { + first = elements.shift(); + } else { + first = make_node(AST_String, self, { value: "" }); + } + return elements.reduce(function(prev, el){ + return make_node(AST_Binary, el, { + operator : "+", + left : prev, + right : el + }); + }, first).optimize(compressor); + } + // need this awkward cloning to not affect original element + // best_of will decide which one to get through. + var node = self.clone(); + node.expression = node.expression.clone(); + node.expression.expression = node.expression.expression.clone(); + node.expression.expression.elements = elements; + return best_of(compressor, self, node); } - // need this awkward cloning to not affect original element - // best_of will decide which one to get through. - var node = self.clone(); - node.expression = node.expression.clone(); - node.expression.expression = node.expression.expression.clone(); - node.expression.expression.elements = elements; - return best_of(compressor, self, node); - } - else if (exp instanceof AST_Dot && exp.expression.is_string(compressor) && exp.property == "charAt") { - var arg = self.args[0]; - var index = arg ? arg.evaluate(compressor) : 0; - if (index !== arg) { - return make_node(AST_Sub, exp, { - expression: exp.expression, - property: make_node_from_constant(index | 0, arg || exp) - }).optimize(compressor); + break; + case "charAt": + if (exp.expression.is_string(compressor)) { + var arg = self.args[0]; + var index = arg ? arg.evaluate(compressor) : 0; + if (index !== arg) { + return make_node(AST_Sub, exp, { + expression: exp.expression, + property: make_node_from_constant(index | 0, arg || exp) + }).optimize(compressor); + } } + break; } } if (compressor.option("unsafe_Func") @@ -4510,16 +4583,21 @@ merge(Compressor.prototype, { d.fixed = fixed = make_node(AST_Function, fixed, fixed); } if (d.single_use && fixed instanceof AST_Function) { - if (!compressor.option("reduce_funcs") && d.scope !== self.scope) { + if (d.scope !== self.scope + && (!compressor.option("reduce_funcs") + || d.escaped + || fixed.inlined)) { d.single_use = false; - } else if (d.escaped && d.scope !== self.scope || recursive_ref(compressor, d)) { + } else if (recursive_ref(compressor, d)) { d.single_use = false; } else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) { d.single_use = fixed.is_constant_expression(self.scope); if (d.single_use == "f") { var scope = self.scope; do { - if (scope.name) scope.name.definition().single_use = false; + if (scope instanceof AST_Defun || scope instanceof AST_Function) { + scope.inlined = true; + } } while (scope = scope.parent_scope); } } diff --git a/lib/scope.js b/lib/scope.js index deeb8092..22408202 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.export = false; this.mangled_name = null; diff --git a/package.json b/package.json index 91078a2a..385d866d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.1.9", + "version": "3.1.10", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 7c76c032..433ae9f2 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3400,3 +3400,483 @@ issue_2453: { } expect_stdout: "42" } + +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; + } + } +} + +issue_2436_13: { + options = { + collapse_vars: true, + reduce_vars: true, + unused: true, + } + input: { + var a = "PASS"; + (function() { + function f(b) { + (function g(b) { + var b = b && (b.null = "FAIL"); + })(a); + } + f(); + })(); + console.log(a); + } + expect: { + var a = "PASS"; + (function() { + (function(b) { + (function(b) { + a && (a.null = "FAIL"); + })(); + })(); + })(); + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2436_14: { + options = { + collapse_vars: true, + reduce_vars: true, + unused: true, + } + input: { + var a = "PASS"; + var b = {}; + (function() { + var c = a; + c && function(c, d) { + console.log(c, d); + }(b, c); + })(); + } + expect: { + var a = "PASS"; + var b = {}; + (function() { + a && function(c, d) { + console.log(c, d); + }(b, a); + })(); + } + expect_stdout: true +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index d8208788..18d09368 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -548,3 +548,92 @@ issue_2462: { }; } } + +issue_2473_1: { + options = { + hoist_props: false, + reduce_vars: true, + top_retain: [ "x", "y" ], + toplevel: true, + unused: true, + } + input: { + var x = {}; + var y = []; + var z = {}; + } + expect: { + var x = {}; + var y = []; + } +} + +issue_2473_2: { + options = { + hoist_props: true, + reduce_vars: true, + top_retain: [ "x", "y" ], + toplevel: true, + unused: true, + } + input: { + var x = {}; + var y = []; + var z = {}; + } + expect: { + var x = {}; + var y = []; + } +} + +issue_2473_3: { + options = { + hoist_props: true, + reduce_vars: true, + top_retain: "o", + toplevel: true, + unused: true, + } + input: { + var o = { + a: 1, + b: 2, + }; + console.log(o.a, o.b); + } + expect: { + var o = { + a: 1, + b: 2, + }; + console.log(o.a, o.b); + } + expect_stdout: "1 2" +} + +issue_2473_4: { + options = { + hoist_props: true, + reduce_vars: true, + top_retain: "o", + toplevel: true, + unused: true, + } + input: { + (function() { + var o = { + a: 1, + b: 2, + }; + console.log(o.a, o.b); + })(); + } + expect: { + (function() { + var o_a = 1, o_b = 2; + console.log(o_a, o_b); + })(); + } + expect_stdout: "1 2" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 20ba90d8..58086ae6 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -4771,3 +4771,51 @@ perf_8: { } expect_stdout: "348150" } + +issue_2485: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + var foo = function(bar) { + var n = function(a, b) { + return a + b; + }; + var sumAll = function(arg) { + return arg.reduce(n, 0); + }; + var runSumAll = function(arg) { + return sumAll(arg); + }; + bar.baz = function(arg) { + var n = runSumAll(arg); + return (n.get = 1), n; + }; + return bar; + }; + var bar = foo({}); + console.log(bar.baz([1, 2, 3])); + } + expect: { + var foo = function(bar) { + var n = function(a, b) { + return a + b; + }; + var runSumAll = function(arg) { + return function(arg) { + return arg.reduce(n, 0); + }(arg); + }; + bar.baz = function(arg) { + var n = runSumAll(arg); + return (n.get = 1), n; + }; + return bar; + }; + var bar = foo({}); + console.log(bar.baz([1, 2, 3])); + } + expect_stdout: "6" +}