diff --git a/README.md b/README.md index a4b09fcd..812ac607 100644 --- a/README.md +++ b/README.md @@ -630,9 +630,6 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `booleans` (default: `true`) -- various optimizations for boolean context, for example `!!a ? b : c → a ? b : c` -- `cascade` (default: `true`) -- small optimization for sequences, transform - `x, x` into `x` and `x = something(), x` into `x = something()` - - `collapse_vars` (default: `true`) -- Collapse single-use non-constant variables, side effects permitting. @@ -665,7 +662,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation) -- `hoist_funs` (default: `true`) -- hoist function declarations +- `hoist_funs` (default: `false`) -- hoist function declarations - `hoist_props` (default: `true`) -- hoist properties from constant object and array literals into regular variables subject to a set of constraints. For example: diff --git a/lib/ast.js b/lib/ast.js index 30b15f85..7407bac9 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -87,7 +87,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before comments_after file raw", { }, null); var AST_Node = DEFNODE("Node", "start end", { diff --git a/lib/compress.js b/lib/compress.js index 2158191c..4c4f91f1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -50,7 +50,6 @@ function Compressor(options, false_by_default) { this.options = defaults(options, { arrows : !false_by_default, booleans : !false_by_default, - cascade : !false_by_default, collapse_vars : !false_by_default, comparisons : !false_by_default, computed_props: !false_by_default, @@ -62,7 +61,7 @@ function Compressor(options, false_by_default) { evaluate : !false_by_default, expression : false, global_defs : {}, - hoist_funs : !false_by_default, + hoist_funs : false, hoist_props : !false_by_default, hoist_vars : false, ie8 : false, @@ -301,14 +300,12 @@ merge(Compressor.prototype, { if (index >= 0) { node.body[index] = node.body[index].transform(tt); } - } - if (node instanceof AST_If) { + } else if (node instanceof AST_If) { node.body = node.body.transform(tt); if (node.alternative) { node.alternative = node.alternative.transform(tt); } - } - if (node instanceof AST_With) { + } else if (node instanceof AST_With) { node.body = node.body.transform(tt); } return node; @@ -316,292 +313,10 @@ merge(Compressor.prototype, { self.transform(tt); }); - AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { - var reduce_vars = compressor.option("reduce_vars"); - var unused = compressor.option("unused"); - // Stack of look-up tables to keep track of whether a `SymbolDef` has been - // properly assigned before use: - // - `push()` & `pop()` when visiting conditional branches - // - backup & restore via `save_ids` when visiting out-of-order sections - var safe_ids = Object.create(null); - var suppressor = new TreeWalker(function(node) { - if (!(node instanceof AST_Symbol)) return; - var d = node.definition(); - if (!d) return; - if (node instanceof AST_SymbolRef) d.references.push(node); - d.fixed = false; - }); - var in_loop = null; - var loop_ids = Object.create(null); - var tw = new TreeWalker(function(node, descend) { - node._squeezed = false; - node._optimized = false; - if (reduce_vars) { - if (node instanceof AST_Toplevel) node.globals.each(reset_def); - if (node instanceof AST_Scope) node.variables.each(reset_def); - if (node.block_scope) node.block_scope.variables.each(reset_def); - if (node instanceof AST_SymbolRef) { - var d = node.definition(); - d.references.push(node); - var value; - if (d.fixed === undefined || !safe_to_read(d) || d.single_use == "m") { - d.fixed = false; - } else if (d.fixed) { - value = node.fixed_value(); - if (value && ref_once(d) && !compressor.exposed(d)) { - d.single_use = value instanceof AST_Lambda - || value instanceof AST_Class - || d.scope === node.scope && value.is_constant_expression(); - } else { - d.single_use = false; - } - if (is_modified(node, value, 0, is_immutable(value))) { - if (d.single_use) { - d.single_use = "m"; - } else { - d.fixed = false; - } - } - } - mark_escaped(d, node.scope, node, value, 0); - } - if (node instanceof AST_SymbolCatch) { - node.definition().fixed = false; - } - if (node instanceof AST_VarDef) { - if (node.name instanceof AST_Destructuring) { - node.name.walk(suppressor); - } else { - var d = node.name.definition(); - if (d.fixed === undefined || safe_to_assign(d, node.value)) { - if (node.value) { - d.fixed = function() { - return node.value; - }; - loop_ids[d.id] = in_loop; - mark(d, false); - descend(); - } else { - d.fixed = null; - } - mark(d, true); - return true; - } else if (node.value) { - d.fixed = false; - } - } - } - if (node instanceof AST_Assign && node.operator == "=") { - if (node.left instanceof AST_Destructuring) { - node.left.walk(suppressor); - } else if (node.left instanceof AST_SymbolRef) { - var d = node.left.definition(); - if (safe_to_assign(d, node.right)) { - d.references.push(node.left); - d.fixed = function() { - return node.right; - }; - mark(d, false); - node.right.walk(tw); - mark(d, true); - return true; - } - } - } - if (node instanceof AST_DefClass || node instanceof AST_Defun) { - node.inlined = false; - var d = node.name.definition(); - if (compressor.exposed(d) || safe_to_read(d)) { - d.fixed = false; - } else { - d.fixed = node; - loop_ids[d.id] = in_loop; - mark(d, true); - d.single_use = ref_once(d); - } - var save_ids = safe_ids; - safe_ids = Object.create(null); - descend(); - safe_ids = save_ids; - return true; - } - if (node instanceof AST_ClassExpression) { - node.inlined = false; - push(); - descend(); - pop(); - return true; - } - if (is_func_expr(node)) { - node.inlined = false; - push(); - var iife; - if (!node.name - && (iife = tw.parent()) instanceof AST_Call - && iife.expression === node) { - // Virtually turn IIFE parameters into variable definitions: - // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() - // So existing transformation rules can work on them. - node.argnames.forEach(function(arg, i) { - if (arg.definition) { - var d = arg.definition(); - if (!node.uses_arguments && d.fixed === undefined) { - d.fixed = function() { - return iife.args[i] || make_node(AST_Undefined, iife); - }; - loop_ids[d.id] = in_loop; - mark(d, true); - } else { - d.fixed = false; - } - } - }); - } - descend(); - pop(); - return true; - } - if (node instanceof AST_Accessor) { - push(); - descend(); - pop(); - return true; - } - if (node instanceof AST_Binary && lazy_op(node.operator)) { - node.left.walk(tw); - push(); - node.right.walk(tw); - pop(); - return true; - } - if (node instanceof AST_Conditional) { - node.condition.walk(tw); - push(); - node.consequent.walk(tw); - pop(); - push(); - node.alternative.walk(tw); - pop(); - return true; - } - if (node instanceof AST_If) { - node.condition.walk(tw); - push(); - node.body.walk(tw); - pop(); - if (node.alternative) { - push(); - node.alternative.walk(tw); - pop(); - } - return true; - } - if (node instanceof AST_DWLoop) { - var saved_loop = in_loop; - in_loop = node; - push(); - node.condition.walk(tw); - node.body.walk(tw); - pop(); - in_loop = saved_loop; - return true; - } - if (node instanceof AST_LabeledStatement) { - push(); - node.body.walk(tw); - pop(); - return true; - } - if (node instanceof AST_For) { - if (node.init) node.init.walk(tw); - var saved_loop = in_loop; - in_loop = node; - if (node.condition) { - push(); - node.condition.walk(tw); - pop(); - } - push(); - node.body.walk(tw); - pop(); - if (node.step) { - push(); - node.step.walk(tw); - pop(); - } - in_loop = saved_loop; - return true; - } - if (node instanceof AST_ForIn) { - node.init.walk(suppressor); - node.object.walk(tw); - var saved_loop = in_loop; - in_loop = node; - push(); - node.body.walk(tw); - pop(); - in_loop = saved_loop; - return true; - } - if (node instanceof AST_Try) { - push(); - walk_body(node, tw); - pop(); - if (node.bcatch) { - push(); - node.bcatch.walk(tw); - pop(); - } - if (node.bfinally) node.bfinally.walk(tw); - return true; - } - if (node instanceof AST_SwitchBranch) { - push(); - descend(); - pop(); - return true; - } - } - }); - this.walk(tw); + (function(def){ + def(AST_Node, noop); - function mark(def, safe) { - safe_ids[def.id] = safe; - } - - function safe_to_read(def) { - if (safe_ids[def.id]) { - if (def.fixed == null) { - var orig = def.orig[0]; - if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false; - def.fixed = make_node(AST_Undefined, orig); - } - return true; - } - return def.fixed instanceof AST_Defun; - } - - function safe_to_assign(def, value) { - if (!HOP(safe_ids, def.id)) return false; - if (!safe_to_read(def)) return false; - if (def.fixed === false) return false; - if (def.fixed != null && (!value || def.references.length > 0)) return false; - return !def.orig.some(function(sym) { - return sym instanceof AST_SymbolConst - || sym instanceof AST_SymbolDefun - || sym instanceof AST_SymbolLambda; - }); - } - - function push() { - safe_ids = Object.create(safe_ids); - } - - function pop() { - safe_ids = Object.getPrototypeOf(safe_ids); - } - - function reset_def(def) { + function reset_def(compressor, def) { def.direct_access = false; def.escaped = false; if (def.scope.uses_eval || def.scope.uses_with) { @@ -616,12 +331,60 @@ merge(Compressor.prototype, { def.single_use = undefined; } - function ref_once(def) { - return unused + function reset_variables(compressor, node) { + node.variables.each(function(def) { + reset_def(compressor, def); + }); + } + + function reset_block_variables(compressor, node) { + if (node.block_scope) node.block_scope.variables.each(function(def) { + reset_def(compressor, def); + }); + } + + function push(tw) { + tw.safe_ids = Object.create(tw.safe_ids); + } + + function pop(tw) { + tw.safe_ids = Object.getPrototypeOf(tw.safe_ids); + } + + function mark(tw, def, safe) { + tw.safe_ids[def.id] = safe; + } + + function safe_to_read(tw, def) { + if (tw.safe_ids[def.id]) { + if (def.fixed == null) { + var orig = def.orig[0]; + if (orig instanceof AST_SymbolFunarg || orig.name == "arguments") return false; + def.fixed = make_node(AST_Undefined, orig); + } + return true; + } + return def.fixed instanceof AST_Defun; + } + + function safe_to_assign(tw, def, value) { + if (!HOP(tw.safe_ids, def.id)) return false; + if (!safe_to_read(tw, def)) return false; + if (def.fixed === false) return false; + if (def.fixed != null && (!value || def.references.length > 0)) return false; + return all(def.orig, function(sym) { + return !(sym instanceof AST_SymbolConst + || sym instanceof AST_SymbolDefun + || sym instanceof AST_SymbolLambda); + }); + } + + function ref_once(tw, compressor, def) { + return compressor.option("unused") && !def.scope.uses_eval && !def.scope.uses_with && def.references.length == 1 - && loop_ids[def.id] === in_loop; + && tw.loop_ids[def.id] === tw.in_loop; } function is_immutable(value) { @@ -650,7 +413,7 @@ merge(Compressor.prototype, { return value instanceof AST_SymbolRef && value.fixed_value() || value; } - function is_modified(node, value, level, immutable) { + function is_modified(tw, node, value, level, immutable) { var parent = tw.parent(level); if (is_lhs(node, parent) || !immutable @@ -662,45 +425,336 @@ merge(Compressor.prototype, { || !(parent instanceof AST_New) && value.contains_this())) { return true; } else if (parent instanceof AST_Array) { - return is_modified(parent, parent, level + 1); + return is_modified(tw, parent, parent, level + 1); } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { var obj = tw.parent(level + 1); - return is_modified(obj, obj, level + 2); + return is_modified(tw, obj, obj, level + 2); } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return !immutable && is_modified(parent, read_property(value, parent.property), level + 1); + return !immutable && is_modified(tw, parent, read_property(value, parent.property), level + 1); } } - function mark_escaped(d, scope, node, value, level) { + function mark_escaped(tw, d, scope, node, value, level, depth) { var parent = tw.parent(level); if (value) { if (value.is_constant()) return; if (value instanceof AST_ClassExpression) return; - if (level > 0 && value.is_constant_expression(scope)) return; } if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right || parent instanceof AST_Call && node !== parent.expression || parent instanceof AST_Exit && node === parent.value && node.scope !== d.scope || parent instanceof AST_VarDef && node === parent.value || parent instanceof AST_Yield && node === parent.value && node.scope !== d.scope) { - d.escaped = true; + if (depth > 1 && !(value && value.is_constant_expression(scope))) depth = 1; + if (!d.escaped || d.escaped > depth) d.escaped = depth; return; } else if (parent instanceof AST_Array || parent instanceof AST_Await + || parent instanceof AST_Binary && lazy_op(parent.operator) || parent instanceof AST_Conditional && node !== parent.condition || parent instanceof AST_Expansion || parent instanceof AST_Sequence && node === parent.tail_node()) { - mark_escaped(d, scope, parent, parent, level + 1); + mark_escaped(tw, d, scope, parent, parent, level + 1, depth); } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { var obj = tw.parent(level + 1); - mark_escaped(d, scope, obj, obj, level + 2); + mark_escaped(tw, d, scope, obj, obj, level + 2, depth); } else if (parent instanceof AST_PropAccess && node === parent.expression) { value = read_property(value, parent.property); - mark_escaped(d, scope, parent, value, level + 1); + mark_escaped(tw, d, scope, parent, value, level + 1, depth + 1); if (value) return; } if (level == 0) d.direct_access = true; } + + var suppressor = new TreeWalker(function(node) { + if (!(node instanceof AST_Symbol)) return; + var d = node.definition(); + if (!d) return; + if (node instanceof AST_SymbolRef) d.references.push(node); + d.fixed = false; + }); + def(AST_Accessor, function(tw, descend) { + push(tw); + descend(); + pop(tw); + return true; + }); + def(AST_Arrow, mark_func_expr); + def(AST_Assign, function(tw) { + var node = this; + if (node.operator != "=" || !(node.left instanceof AST_SymbolRef)) return; + var d = node.left.definition(); + if (safe_to_assign(tw, d, node.right) + || d.fixed === undefined && all(d.orig, function(sym) { + return sym instanceof AST_SymbolVar; + })) { + d.references.push(node.left); + d.fixed = function() { + return node.right; + }; + mark(tw, d, false); + node.right.walk(tw); + mark(tw, d, true); + return true; + } + }); + def(AST_Binary, function(tw) { + if (!lazy_op(this.operator)) return; + this.left.walk(tw); + push(tw); + this.right.walk(tw); + pop(tw); + return true; + }); + def(AST_Block, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + }); + def(AST_ClassExpression, function(tw, descend) { + this.inlined = false; + push(tw); + descend(); + pop(tw); + return true; + }) + def(AST_Conditional, function(tw) { + this.condition.walk(tw); + push(tw); + this.consequent.walk(tw); + pop(tw); + push(tw); + this.alternative.walk(tw); + pop(tw); + return true; + }); + + function mark_def_node(tw, descend, compressor) { + reset_variables(compressor, this); + this.inlined = false; + var d = this.name.definition(); + if (compressor.exposed(d) || safe_to_read(tw, d)) { + d.fixed = false; + } else { + d.fixed = this; + d.single_use = ref_once(tw, compressor, d); + tw.loop_ids[d.id] = tw.in_loop; + mark(tw, d, true); + } + var save_ids = tw.safe_ids; + tw.safe_ids = Object.create(null); + descend(); + tw.safe_ids = save_ids; + return true; + } + + def(AST_DefClass, mark_def_node); + def(AST_Defun, mark_def_node); + def(AST_Do, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + var saved_loop = tw.in_loop; + tw.in_loop = this; + push(tw); + this.body.walk(tw); + this.condition.walk(tw); + pop(tw); + tw.in_loop = saved_loop; + return true; + }); + def(AST_For, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + if (this.init) this.init.walk(tw); + var saved_loop = tw.in_loop; + tw.in_loop = this; + if (this.condition) { + push(tw); + this.condition.walk(tw); + pop(tw); + } + push(tw); + this.body.walk(tw); + pop(tw); + if (this.step) { + push(tw); + this.step.walk(tw); + pop(tw); + } + tw.in_loop = saved_loop; + return true; + }); + def(AST_ForIn, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + this.init.walk(suppressor); + this.object.walk(tw); + var saved_loop = tw.in_loop; + tw.in_loop = this; + push(tw); + this.body.walk(tw); + pop(tw); + tw.in_loop = saved_loop; + return true; + }); + + function mark_func_expr(tw, descend, compressor) { + var node = this; + reset_variables(compressor, node); + node.inlined = false; + push(tw); + var iife; + if (!node.name + && (iife = tw.parent()) instanceof AST_Call + && iife.expression === node) { + // Virtually turn IIFE parameters into variable definitions: + // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() + // So existing transformation rules can work on them. + node.argnames.forEach(function(arg, i) { + if (!arg.definition) return; + var d = arg.definition(); + if (!node.uses_arguments && d.fixed === undefined) { + d.fixed = function() { + return iife.args[i] || make_node(AST_Undefined, iife); + }; + tw.loop_ids[d.id] = tw.in_loop; + mark(tw, d, true); + } else { + d.fixed = false; + } + }); + } + descend(); + pop(tw); + return true; + } + + def(AST_Function, mark_func_expr); + def(AST_If, function(tw) { + this.condition.walk(tw); + push(tw); + this.body.walk(tw); + pop(tw); + if (this.alternative) { + push(tw); + this.alternative.walk(tw); + pop(tw); + } + return true; + }); + def(AST_LabeledStatement, function(tw) { + push(tw); + this.body.walk(tw); + pop(tw); + return true; + }); + def(AST_SwitchBranch, function(tw, descend) { + push(tw); + descend(); + pop(tw); + return true; + }); + def(AST_SymbolCatch, function() { + this.definition().fixed = false; + }); + def(AST_SymbolRef, function(tw, descend, compressor) { + var d = this.definition(); + d.references.push(this); + if (d.references.length == 1 + && !d.fixed + && d.orig[0] instanceof AST_SymbolDefun) { + tw.loop_ids[d.id] = tw.in_loop; + } + var value; + if (d.fixed === undefined || !safe_to_read(tw, d) || d.single_use == "m") { + d.fixed = false; + } else if (d.fixed) { + value = this.fixed_value(); + if (value && !compressor.exposed(d) && ref_once(tw, compressor, d)) { + d.single_use = value instanceof AST_Lambda + || value instanceof AST_Class + || d.scope === this.scope && value.is_constant_expression(); + } else { + d.single_use = false; + } + if (is_modified(tw, this, value, 0, is_immutable(value))) { + if (d.single_use) { + d.single_use = "m"; + } else { + d.fixed = false; + } + } + } + mark_escaped(tw, d, this.scope, this, value, 0, 1); + }); + def(AST_Toplevel, function(tw, descend, compressor) { + this.globals.each(function(def) { + reset_def(compressor, def); + }); + reset_variables(compressor, this); + }); + def(AST_Try, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + push(tw); + walk_body(this, tw); + pop(tw); + if (this.bcatch) { + push(tw); + this.bcatch.walk(tw); + pop(tw); + } + if (this.bfinally) this.bfinally.walk(tw); + return true; + }); + def(AST_VarDef, function(tw, descend) { + var node = this; + if (node.name instanceof AST_Destructuring) { + node.name.walk(suppressor); + return true; + } + var d = node.name.definition(); + if (d.fixed === undefined || safe_to_assign(tw, d, node.value)) { + if (node.value) { + d.fixed = function() { + return node.value; + }; + tw.loop_ids[d.id] = tw.in_loop; + mark(tw, d, false); + descend(); + } else { + d.fixed = null; + } + mark(tw, d, true); + return true; + } else if (node.value) { + d.fixed = false; + } + }); + def(AST_While, function(tw, descend, compressor) { + reset_block_variables(compressor, this); + var saved_loop = tw.in_loop; + tw.in_loop = this; + push(tw); + this.condition.walk(tw); + this.body.walk(tw); + pop(tw); + tw.in_loop = saved_loop; + return true; + }); + })(function(node, func){ + node.DEFMETHOD("reduce_vars", func); + }); + + AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) { + var reduce_vars = compressor.option("reduce_vars"); + var tw = new TreeWalker(function(node, descend) { + node._squeezed = false; + node._optimized = false; + if (reduce_vars) return node.reduce_vars(tw, descend, compressor); + }); + // Stack of look-up tables to keep track of whether a `SymbolDef` has been + // properly assigned before use: + // - `push()` & `pop()` when visiting conditional branches + // - backup & restore via `save_ids` when visiting out-of-order sections + tw.safe_ids = Object.create(null); + tw.in_loop = null; + tw.loop_ids = Object.create(null); + this.walk(tw); }); AST_Symbol.DEFMETHOD("fixed_value", function() { @@ -862,10 +916,8 @@ merge(Compressor.prototype, { }; function is_iife_call(node) { - if (node instanceof AST_Call && !(node instanceof AST_New)) { - return node.expression instanceof AST_Function || is_iife_call(node.expression); - } - return false; + if (node.TYPE != "Call") return false; + return node.expression instanceof AST_Function || is_iife_call(node.expression); } function is_undeclared_ref(node) { @@ -878,6 +930,7 @@ merge(Compressor.prototype, { || compressor.option("unsafe") && global_names(this.name); }); + var identifier_atom = makePredicate("Infinity NaN undefined"); function is_identifier_atom(node) { return node instanceof AST_Infinity || node instanceof AST_NaN @@ -919,16 +972,40 @@ merge(Compressor.prototype, { if (scope.uses_eval || scope.uses_with) return statements; var args; var candidates = []; + var in_try = compressor.self() instanceof AST_Try; var stat_index = statements.length; var scanner = new TreeTransformer(function(node, descend) { if (abort) return node; + // Scan case expressions first in a switch statement + if (node instanceof AST_Switch) { + if (!hit) { + if (node !== hit_stack[hit_index]) return node; + hit_index++; + } + node.expression = node.expression.transform(scanner); + for (var i = 0, len = node.body.length; !abort && i < len; i++) { + var branch = node.body[i]; + if (branch instanceof AST_Case) { + if (!hit) { + if (branch !== hit_stack[hit_index]) continue; + hit_index++; + } + branch.expression = branch.expression.transform(scanner); + if (side_effects || !replace_all) break; + } + } + abort = true; + return node; + } // Skip nodes before `candidate` as quickly as possible if (!hit) { - if (node === candidate) { - hit = true; - return node; - } - return; + if (node !== hit_stack[hit_index]) return node; + hit_index++; + if (hit_index < hit_stack.length) return; + hit = true; + stop_after = find_stop(node, 0); + if (stop_after === node) abort = true; + return node; } // Stop immediately if these node types are encountered var parent = scanner.parent(); @@ -938,10 +1015,11 @@ merge(Compressor.prototype, { || 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) { + || parent instanceof AST_For && node !== parent.init + || (side_effects || !replace_all) + && (node instanceof AST_SymbolRef && !node.is_declared(compressor))) { abort = true; return node; } @@ -1000,27 +1078,29 @@ merge(Compressor.prototype, { || side_effects && !references_in_scope(node.definition())) || (sym = lhs_or_def(node)) && (sym instanceof AST_PropAccess || sym.name in lvalues) + || may_throw + && (in_try ? node.has_side_effects(compressor) : side_effects_external(node)) || (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; + stop_after = node; + if (node instanceof AST_Scope) abort = true; } - // Skip (non-executed) functions and (leading) default case in switch statements - if (node instanceof AST_Default || node instanceof AST_Scope) return node; + // Skip (non-executed) functions + if (node instanceof AST_Scope) return node; + }, function(node) { + if (!abort && stop_after === node) abort = true; }); 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; + if (node !== hit_stack[hit_index]) return node; + hit_index++; + if (hit_index < hit_stack.length) return; + hit = true; + return node; } // Replace variable when found if (node instanceof AST_SymbolRef @@ -1041,10 +1121,14 @@ merge(Compressor.prototype, { // var a = x(), b = undefined; if (stat_index == 0 && compressor.option("unused")) extract_args(); // Find collapsible assignments + var hit_stack = []; extract_candidates(statements[stat_index]); while (candidates.length > 0) { - var candidate = candidates.pop(); + hit_stack = candidates.pop(); + var hit_index = 0; + var candidate = hit_stack[hit_stack.length - 1]; var value_def = null; + var stop_after = null; var lhs = get_lhs(candidate); if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor)) continue; // Locate symbols which may execute code outside of scanning range @@ -1053,9 +1137,12 @@ merge(Compressor.prototype, { var replace_all = value_def; if (!replace_all && lhs instanceof AST_SymbolRef) { var def = lhs.definition(); - replace_all = def.references.length - def.replaced == 1; + if (def.references.length - def.replaced == (candidate instanceof AST_VarDef ? 1 : 2)) { + replace_all = true; + } } var side_effects = value_has_side_effects(candidate); + var may_throw = candidate.may_throw(compressor); var funarg = candidate.name instanceof AST_SymbolFunarg; var hit = funarg; var abort = false, replaced = 0, can_replace = !args || !hit; @@ -1073,10 +1160,12 @@ merge(Compressor.prototype, { if (abort && def.references.length - def.replaced > replaced) replaced = false; else { abort = false; + hit_index = 0; hit = funarg; for (var i = stat_index; !abort && i < statements.length; i++) { statements[i].transform(multi_replacer); } + value_def.single_use = false; } } if (replaced && !remove_candidate(candidate)) statements.splice(stat_index, 1); @@ -1138,42 +1227,76 @@ merge(Compressor.prototype, { if (all(elements, function(arg) { return !has_overlapping_symbol(fn, arg, fn_strict); })) { - candidates.unshift(make_node(AST_VarDef, sym, { + candidates.unshift([ make_node(AST_VarDef, sym, { name: sym.expression, value: make_node(AST_Array, iife, { elements: elements }) - })); - candidates[0].__name = sym; + }) ]); } } else { if (!arg) arg = make_node(AST_Undefined, sym).transform(compressor); else if (has_overlapping_symbol(fn, arg, fn_strict)) arg = null; - if (arg) candidates.unshift(make_node(AST_VarDef, sym, { + if (arg) candidates.unshift([ make_node(AST_VarDef, sym, { name: sym, value: arg - })); + }) ]); } } } } function extract_candidates(expr) { - if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) - || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) { - candidates.push(expr); - } else if (expr instanceof AST_Sequence) { - expr.expressions.forEach(extract_candidates); + hit_stack.push(expr); + if (expr instanceof AST_Assign) { + if (!expr.left.has_side_effects(compressor)) { + candidates.push(hit_stack.slice()); + } + } else if (expr instanceof AST_Unary) { + if (expr.operator == "++" || expr.operator == "--") { + candidates.push(hit_stack.slice()); + } + } else if (expr instanceof AST_Call) { + extract_candidates(expr.expression); + expr.args.forEach(extract_candidates); + } else if (expr instanceof AST_Case) { + extract_candidates(expr.expression); + } else if (expr instanceof AST_Conditional) { + extract_candidates(expr.condition); + extract_candidates(expr.consequent); + extract_candidates(expr.alternative); } else if (expr instanceof AST_Definitions && (compressor.option("unused") || !(expr instanceof AST_Const))) { - expr.definitions.forEach(function(var_def) { - if (var_def.value) candidates.push(var_def); - }); + expr.definitions.forEach(extract_candidates); + } else if (expr instanceof AST_Exit) { + if (expr.value) extract_candidates(expr.value); + } else if (expr instanceof AST_For) { + if (expr.init) extract_candidates(expr.init); + } else if (expr instanceof AST_If) { + extract_candidates(expr.condition); + } else if (expr instanceof AST_Sequence) { + expr.expressions.forEach(extract_candidates); } else if (expr instanceof AST_SimpleStatement) { extract_candidates(expr.body); - } else if (expr instanceof AST_For && expr.init) { - extract_candidates(expr.init); + } else if (expr instanceof AST_Switch) { + extract_candidates(expr.expression); + expr.body.forEach(extract_candidates); + } else if (expr instanceof AST_VarDef) { + if (expr.value) candidates.push(hit_stack.slice()); } + hit_stack.pop(); + } + + function find_stop(node, level) { + var parent = scanner.parent(level); + if (parent instanceof AST_Call) return node; + if (parent instanceof AST_Case) return node; + if (parent instanceof AST_Conditional) return node; + if (parent instanceof AST_Exit) return node; + if (parent instanceof AST_If) return node; + if (parent instanceof AST_Sequence) return find_stop(parent, level + 1); + if (parent instanceof AST_Switch) return node; + return null; } function mangleable_var(var_def) { @@ -1269,6 +1392,25 @@ merge(Compressor.prototype, { return ref.scope.get_defun_scope() === scope; }); } + + function side_effects_external(node, lhs) { + if (node instanceof AST_Assign) { + return side_effects_external(node.left, true) + || side_effects_external(node.right); + } + if (node instanceof AST_Definitions) return false; + if (node instanceof AST_Unary) return side_effects_external(node.expression, true); + if (node instanceof AST_VarDef) return node.value && side_effects_external(node.value); + if (lhs) { + if (node instanceof AST_Dot) return side_effects_external(node.expression, true); + if (node instanceof AST_Sub) { + return side_effects_external(node.expression, true) + || side_effects_external(node.property); + } + if (node instanceof AST_SymbolRef) return node.definition().scope !== scope; + } + return node.has_side_effects(compressor); + } } function eliminate_spurious_blocks(statements) { @@ -1692,6 +1834,11 @@ merge(Compressor.prototype, { return this.consequent._dot_throw(compressor) || this.alternative._dot_throw(compressor); }) + def(AST_Dot, function(compressor) { + if (!is_strict(compressor)) return false; + if (this.expression instanceof AST_Function && this.property == "prototype") return false; + return true; + }); def(AST_Sequence, function(compressor) { return this.tail_node()._dot_throw(compressor); }); @@ -1890,7 +2037,7 @@ merge(Compressor.prototype, { // descendant of AST_Node. AST_Node.DEFMETHOD("evaluate", function(compressor){ if (!compressor.option("evaluate")) return this; - var val = this._eval(compressor); + var val = this._eval(compressor, 1); return !val || val instanceof RegExp || typeof val != "object" ? val : this; }); var unaryPrefix = makePredicate("! ~ - + void"); @@ -1910,11 +2057,6 @@ merge(Compressor.prototype, { }); def(AST_Lambda, return_this); def(AST_Class, return_this); - function ev(node, compressor) { - if (!compressor) throw new Error("Compressor must be passed"); - - return node._eval(compressor); - }; def(AST_Node, return_this); def(AST_Constant, function(){ return this.getValue(); @@ -1923,13 +2065,13 @@ merge(Compressor.prototype, { if (this.segments.length !== 1) return this; return this.segments[0].value; }); - def(AST_Array, function(compressor){ + def(AST_Array, function(compressor, depth) { if (compressor.option("unsafe")) { var elements = []; for (var i = 0, len = this.elements.length; i < len; i++) { var element = this.elements[i]; if (element instanceof AST_Function) continue; - var value = ev(element, compressor); + var value = element._eval(compressor, depth); if (element === value) return this; elements.push(value); } @@ -1937,7 +2079,7 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Object, function(compressor){ + def(AST_Object, function(compressor, depth) { if (compressor.option("unsafe")) { var val = {}; for (var i = 0, len = this.properties.length; i < len; i++) { @@ -1947,21 +2089,21 @@ merge(Compressor.prototype, { if (key instanceof AST_Symbol) { key = key.name; } else if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === prop.key) return this; } if (typeof Object.prototype[key] === 'function') { return this; } if (prop.value instanceof AST_Function) continue; - val[key] = ev(prop.value, compressor); + val[key] = prop.value._eval(compressor, depth); if (val[key] === prop.value) return this; } return val; } return this; }); - def(AST_UnaryPrefix, function(compressor){ + def(AST_UnaryPrefix, function(compressor, depth) { var e = this.expression; // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. @@ -1972,7 +2114,7 @@ merge(Compressor.prototype, { && e.fixed_value() instanceof AST_Lambda)) { return typeof function(){}; } - e = ev(e, compressor); + e = e._eval(compressor, depth); if (e === this.expression) return this; switch (this.operator) { case "!": return !e; @@ -1988,10 +2130,10 @@ merge(Compressor.prototype, { } return this; }); - def(AST_Binary, function(compressor){ - var left = ev(this.left, compressor); + def(AST_Binary, function(compressor, depth) { + var left = this.left._eval(compressor, depth); if (left === this.left) return this; - var right = ev(this.right, compressor); + var right = this.right._eval(compressor, depth); if (right === this.right) return this; var result; switch (this.operator) { @@ -2026,30 +2168,32 @@ merge(Compressor.prototype, { } return result; }); - def(AST_Conditional, function(compressor){ - var condition = ev(this.condition, compressor); + def(AST_Conditional, function(compressor, depth) { + var condition = this.condition._eval(compressor, depth); if (condition === this.condition) return this; var node = condition ? this.consequent : this.alternative; - var value = ev(node, compressor); + var value = node._eval(compressor, depth); return value === node ? this : value; }); - def(AST_SymbolRef, function(compressor){ + def(AST_SymbolRef, function(compressor, depth) { var fixed = this.fixed_value(); if (!fixed) return this; - this._eval = return_this; - var value = ev(fixed, compressor); - if (value === fixed) { + var value; + if (HOP(fixed, "_eval")) { + value = fixed._eval(); + } else { + this._eval = return_this; + value = fixed._eval(compressor, depth); delete this._eval; - return this; + if (value === fixed) return this; + fixed._eval = function() { + return value; + }; } - if (!HOP(fixed, "_eval")) fixed._eval = function() { - return value; - }; - if (value && typeof value == "object" && this.definition().escaped) { - delete this._eval; - return this; + if (value && typeof value == "object") { + var escaped = this.definition().escaped; + if (escaped && depth > escaped) return this; } - this._eval = fixed._eval; return value; }); var global_objs = { @@ -2083,11 +2227,11 @@ merge(Compressor.prototype, { ], }; convert_to_predicate(static_values); - def(AST_PropAccess, function(compressor){ + def(AST_PropAccess, function(compressor, depth) { if (compressor.option("unsafe")) { var key = this.property; if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === this.property) return this; } var exp = this.expression; @@ -2096,7 +2240,7 @@ merge(Compressor.prototype, { if (!(static_values[exp.name] || return_false)(key)) return this; val = global_objs[exp.name]; } else { - val = ev(exp, compressor); + val = exp._eval(compressor, depth + 1); if (!val || val === exp || !HOP(val, key)) return this; } return val[key]; @@ -2174,12 +2318,12 @@ merge(Compressor.prototype, { ], }; convert_to_predicate(static_fns); - def(AST_Call, function(compressor){ + def(AST_Call, function(compressor, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { var key = exp.property; if (key instanceof AST_Node) { - key = ev(key, compressor); + key = key._eval(compressor, depth); if (key === exp.property) return this; } var val; @@ -2188,13 +2332,13 @@ merge(Compressor.prototype, { if (!(static_fns[e.name] || return_false)(key)) return this; val = global_objs[e.name]; } else { - val = ev(e, compressor); + val = e._eval(compressor, depth + 1); if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this; } var args = []; for (var i = 0, len = this.args.length; i < len; i++) { var arg = this.args[i]; - var value = ev(arg, compressor); + var value = arg._eval(compressor, depth); if (arg === value) return this; args.push(value); } @@ -2287,29 +2431,13 @@ merge(Compressor.prototype, { }); }); - AST_Call.DEFMETHOD("has_pure_annotation", function(compressor) { - if (!compressor.option("side_effects")) return false; - if (this.pure !== undefined) return this.pure; - var pure = false; - var comments, pure_comment; - if (this.start - && (comments = this.start.comments_before) - && comments.length - && (pure_comment = find_if(function (comment) { - return /[@#]__PURE__/.test(comment.value); - }, comments))) { - pure = pure_comment; - } - return this.pure = pure; - }); - var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError"); AST_Call.DEFMETHOD("is_expr_pure", function(compressor) { if (compressor.option("unsafe")) { var expr = this.expression; if (is_undeclared_ref(expr) && global_pure_fns(expr.name)) return true; } - return this.has_pure_annotation(compressor) || !compressor.pure_funcs(this); + return this.pure || !compressor.pure_funcs(this); }); // determine if expression has side effects @@ -2320,15 +2448,6 @@ merge(Compressor.prototype, { def(AST_Constant, return_false); def(AST_This, return_false); - def(AST_Call, function(compressor){ - if (!this.is_expr_pure(compressor)) return true; - for (var i = this.args.length; --i >= 0;) { - if (this.args[i].has_side_effects(compressor)) - return true; - } - return false; - }); - function any(list, compressor) { for (var i = list.length; --i >= 0;) if (list[i].has_side_effects(compressor)) @@ -2339,6 +2458,10 @@ merge(Compressor.prototype, { def(AST_Block, function(compressor){ return any(this.body, compressor); }); + def(AST_Call, function(compressor){ + return !this.is_expr_pure(compressor) + || any(this.args, compressor); + }); def(AST_Switch, function(compressor){ return this.expression.has_side_effects(compressor) || any(this.body, compressor); @@ -2363,9 +2486,7 @@ merge(Compressor.prototype, { def(AST_SimpleStatement, function(compressor){ return this.body.has_side_effects(compressor); }); - def(AST_Defun, return_true); - def(AST_Function, return_false); - def(AST_Arrow, return_false); + def(AST_Lambda, return_false); def(AST_Class, return_false); def(AST_DefClass, return_true); def(AST_Binary, function(compressor){ @@ -2408,9 +2529,13 @@ merge(Compressor.prototype, { || this.property.has_side_effects(compressor); }); def(AST_Sequence, function(compressor){ - return this.expressions.some(function(expression, index) { - return expression.has_side_effects(compressor); - }); + return any(this.expressions, compressor); + }); + def(AST_Definitions, function(compressor){ + return any(this.definitions, compressor); + }); + def(AST_VarDef, function(compressor){ + return this.value; }); def(AST_TemplateSegment, return_false); def(AST_TemplateString, function(compressor){ @@ -2420,6 +2545,111 @@ merge(Compressor.prototype, { node.DEFMETHOD("has_side_effects", func); }); + // determine if expression may throw + (function(def){ + def(AST_Node, return_true); + + def(AST_Class, return_false); + def(AST_Constant, return_false); + def(AST_EmptyStatement, return_false); + def(AST_Lambda, return_false); + def(AST_SymbolDeclaration, return_false); + def(AST_This, return_false); + + function any(list, compressor) { + for (var i = list.length; --i >= 0;) + if (list[i].may_throw(compressor)) + return true; + return false; + } + + def(AST_Array, function(compressor){ + return any(this.elements, compressor); + }); + def(AST_Assign, function(compressor){ + return this.operator != "=" && this.left.may_throw(compressor) + || this.right.may_throw(compressor); + }); + def(AST_Binary, function(compressor){ + return this.left.may_throw(compressor) + || this.right.may_throw(compressor); + }); + def(AST_Block, function(compressor){ + return any(this.body, compressor); + }); + def(AST_Call, function(compressor){ + if (any(this.args, compressor)) return true; + if (this.is_expr_pure(compressor)) return false; + if (this.expression.may_throw(compressor)) return true; + return !(this.expression instanceof AST_Lambda) + || any(this.expression.body, compressor); + }); + def(AST_Case, function(compressor){ + return this.expression.may_throw(compressor) + || any(this.body, compressor); + }); + def(AST_Conditional, function(compressor){ + return this.condition.may_throw(compressor) + || this.consequent.may_throw(compressor) + || this.alternative.may_throw(compressor); + }); + def(AST_Definitions, function(compressor){ + return any(this.definitions, compressor); + }); + def(AST_Dot, function(compressor){ + return this.expression.may_throw_on_access(compressor) + || this.expression.may_throw(compressor); + }); + def(AST_If, function(compressor){ + return this.condition.may_throw(compressor) + || this.body && this.body.may_throw(compressor) + || this.alternative && this.alternative.may_throw(compressor); + }); + def(AST_LabeledStatement, function(compressor){ + return this.body.may_throw(compressor); + }); + def(AST_Object, function(compressor){ + return any(this.properties, compressor); + }); + def(AST_ObjectProperty, function(compressor){ + return this.value.may_throw(compressor); + }); + def(AST_Sequence, function(compressor){ + return any(this.expressions, compressor); + }); + def(AST_SimpleStatement, function(compressor){ + return this.body.may_throw(compressor); + }); + def(AST_Sub, function(compressor){ + return this.expression.may_throw_on_access(compressor) + || this.expression.may_throw(compressor) + || this.property.may_throw(compressor); + }); + def(AST_Switch, function(compressor){ + return this.expression.may_throw(compressor) + || any(this.body, compressor); + }); + def(AST_SymbolRef, function(compressor){ + return !this.is_declared(compressor); + }); + def(AST_Try, function(compressor){ + return any(this.body, compressor) + || this.bcatch && this.bcatch.may_throw(compressor) + || this.bfinally && this.bfinally.may_throw(compressor); + }); + def(AST_Unary, function(compressor){ + if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) + return false; + return this.expression.may_throw(compressor); + }); + def(AST_VarDef, function(compressor){ + if (!this.value) return false; + return this.value.may_throw(compressor); + }); + })(function(node, func){ + node.DEFMETHOD("may_throw", func); + }); + // determine if expression is constant (function(def){ function all(list) { @@ -3011,10 +3241,10 @@ merge(Compressor.prototype, { return self; }); - AST_Scope.DEFMETHOD("make_var_name", function(prefix) { - var var_names = this.var_names; + AST_Scope.DEFMETHOD("var_names", function() { + var var_names = this._var_names; if (!var_names) { - this.var_names = var_names = Object.create(null); + this._var_names = var_names = Object.create(null); this.enclosed.forEach(function(def) { var_names[def.name] = true; }); @@ -3022,6 +3252,11 @@ merge(Compressor.prototype, { var_names[name] = true; }); } + return var_names; + }); + + AST_Scope.DEFMETHOD("make_var_name", function(prefix) { + var var_names = this.var_names(); prefix = prefix.replace(/[^a-z_$]+/ig, "_"); var name = prefix; for (var i = 0; var_names[name]; i++) name = prefix + "$" + i; @@ -3039,7 +3274,7 @@ merge(Compressor.prototype, { if (node instanceof AST_VarDef) { var sym = node.name, def, value; if (sym.scope === self - && !(def = sym.definition()).escaped + && (def = sym.definition()).escaped != 1 && !def.single_use && !def.direct_access && !top_retain(def) @@ -3125,7 +3360,6 @@ merge(Compressor.prototype, { } if (this.pure) { compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); - this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); } var args = trim(this.args, compressor, first_in_statement); return args && make_sequence(this, args); @@ -3149,8 +3383,14 @@ merge(Compressor.prototype, { } }); def(AST_Assign, function(compressor){ - this.write_only = !this.left.has_side_effects(compressor); - return this; + var left = this.left; + if (left.has_side_effects(compressor)) return this; + this.write_only = true; + while (left instanceof AST_PropAccess) { + left = left.expression; + } + if (left instanceof AST_Symbol) return this; + return this.right.drop_side_effect_free(compressor); }); def(AST_Conditional, function(compressor){ var consequent = this.consequent.drop_side_effect_free(compressor); @@ -3178,14 +3418,9 @@ merge(Compressor.prototype, { } if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) return null; var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); - if (first_in_statement - && this instanceof AST_UnaryPrefix - && is_iife_call(expression)) { - if (expression === this.expression && this.operator.length === 1) return this; - return make_node(AST_UnaryPrefix, this, { - operator: this.operator.length === 1 ? this.operator : "!", - expression: expression - }); + if (first_in_statement && expression && is_iife_call(expression)) { + if (expression === this.expression && this.operator == "!") return this; + return expression.negate(compressor, first_in_statement); } return expression; }); @@ -3874,6 +4109,34 @@ merge(Compressor.prototype, { } } break; + case "apply": + if (self.args.length == 2 && self.args[1] instanceof AST_Array) { + var args = self.args[1].elements.slice(); + args.unshift(self.args[0]); + return make_node(AST_Call, self, { + expression: make_node(AST_Dot, exp, { + expression: exp.expression, + property: "call" + }), + args: args + }).optimize(compressor); + } + break; + case "call": + var func = exp.expression; + if (func instanceof AST_SymbolRef) { + func = func.fixed_value(); + } + if (func instanceof AST_Function && !func.contains_this()) { + return make_sequence(this, [ + self.args[0], + make_node(AST_Call, self, { + expression: exp.expression, + args: self.args.slice(1) + }) + ]).optimize(compressor); + } + break; } } if (compressor.option("unsafe_Func") @@ -3957,32 +4220,24 @@ merge(Compressor.prototype, { } } if (is_func_expr(fn) && !fn.is_generator && !fn.async) { + var def, scope, value; if (compressor.option("inline") - && exp === fn && simple_args - && !fn.name && !fn.uses_arguments && !fn.uses_eval && (fn.body instanceof AST_Node || fn.body.length == 1) + && (exp === fn ? !fn.name + : compressor.option("unused") + && (def = exp.definition()).references.length == 1 + && !recursive_ref(compressor, def) + && fn.is_constant_expression(exp.scope)) + && !self.pure && !fn.contains_this() - && all(fn.argnames, function(arg) { - if (arg instanceof AST_Expansion) return arg.expression.__unused; - return arg.__unused; - }) - && !self.has_pure_annotation(compressor)) { - var value; - if (stat instanceof AST_Return) { - value = stat.value; - } else if (stat instanceof AST_SimpleStatement) { - value = make_node(AST_UnaryPrefix, stat, { - operator: "void", - expression: stat.body - }); - } - if (value) { - var args = self.args.concat(value); - return make_sequence(self, args).optimize(compressor); - } + && (scope = can_flatten_args(fn)) + && (value = flatten_body(stat))) { + var expressions = flatten_args(fn, scope); + expressions.push(value.clone(true)); + return make_sequence(self, expressions).optimize(compressor); } if (compressor.option("side_effects") && !(fn.body instanceof AST_Node) && all(fn.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); @@ -4011,6 +4266,82 @@ merge(Compressor.prototype, { return best_of(compressor, ev, self); } return self; + + function can_flatten_args(fn) { + var scope, level = 0; + var catches = Object.create(null); + do { + scope = compressor.parent(level++); + if (scope instanceof AST_SymbolRef) { + scope = scope.fixed_value(); + } else if (scope instanceof AST_Catch) { + catches[scope.argname.name] = true; + } + } while (!(scope instanceof AST_Scope)); + var safe_to_inject = compressor.toplevel.vars || !(scope instanceof AST_Toplevel); + return all(fn.argnames, function(arg) { + if (arg instanceof AST_Expansion) return arg.expression.__unused; + return arg.__unused + || safe_to_inject + && !catches[arg.name] + && !identifier_atom(arg.name) + && !scope.var_names()[arg.name]; + }) && scope; + } + + function flatten_args(fn, scope) { + var decls = []; + var expressions = []; + for (var len = fn.argnames.length, i = len; --i >= 0;) { + var name = fn.argnames[i]; + var value = self.args[i]; + if (name.__unused || name instanceof AST_Expansion) { + if (value || expressions.length) { + expressions.unshift(value || make_node(AST_Undefined, self)); + } + } else { + var def = name.definition(); + scope.var_names()[name.name] = true; + scope.variables.set(name.name, def); + scope.enclosed.push(def); + var symbol = make_node(AST_SymbolVar, name, name); + def.orig.push(symbol); + decls.unshift(make_node(AST_VarDef, name, { + name: symbol, + value: null + })); + var sym = make_node(AST_SymbolRef, name, name); + def.references.push(sym); + expressions.unshift(make_node(AST_Assign, self, { + operator: "=", + left: sym, + right: value || make_node(AST_Undefined, self) + })); + } + } + for (i = len, len = self.args.length; i < len; i++) { + expressions.push(self.args[i]); + } + if (decls.length) { + for (i = 0; compressor.parent(i) !== scope;) i++; + i = scope.body.indexOf(compressor.parent(i - 1)) + 1; + scope.body.splice(i, 0, make_node(AST_Var, fn, { + definitions: decls + })); + } + return expressions; + } + + function flatten_body(stat) { + if (stat instanceof AST_Return) { + return stat.value; + } else if (stat instanceof AST_SimpleStatement) { + return make_node(AST_UnaryPrefix, stat, { + operator: "void", + expression: stat.body + }); + } + } }); OPT(AST_New, function(self, compressor){ @@ -4036,7 +4367,6 @@ merge(Compressor.prototype, { filter_for_side_effects(); var end = expressions.length - 1; trim_right_for_undefined(); - if (end > 0 && compressor.option("cascade")) trim_left_for_assignment(); if (end == 0) { self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]); if (!(self instanceof AST_Sequence)) self = self.optimize(compressor); @@ -4067,74 +4397,6 @@ merge(Compressor.prototype, { expressions.length = end + 1; } } - - function trim_left_for_assignment() { - for (var i = 0, j = 1; j <= end; j++) { - var left = expressions[i]; - var cdr = expressions[j]; - if (left instanceof AST_Assign - && !left.left.has_side_effects(compressor)) { - left = left.left; - } else if (left instanceof AST_Unary - && (left.operator == "++" || left.operator == "--")) { - left = left.expression; - } else left = null; - if (!left - || is_lhs_read_only(left) - || left.has_side_effects(compressor) - || is_ref_of(left, AST_SymbolConst)) { - expressions[++i] = cdr; - continue; - } - var parent = null, field; - expressions[j] = cdr = cdr.clone(); - while (true) { - if (cdr.equivalent_to(left)) { - var car = expressions[i]; - if (car instanceof AST_UnaryPostfix) { - car = make_node(AST_UnaryPrefix, car, { - operator: car.operator, - expression: left - }); - } else { - car.write_only = false; - } - if (parent) { - parent[field] = car; - expressions[i] = expressions[j]; - } else { - expressions[i] = car; - } - break; - } - if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) { - if (cdr.left.is_constant()) { - if (lazy_op(cdr.operator)) { - expressions[++i] = expressions[j]; - break; - } - field = "right"; - } else { - field = "left"; - } - } else if (cdr instanceof AST_Call - && !(left instanceof AST_PropAccess && cdr.expression.equivalent_to(left)) - || cdr instanceof AST_PropAccess - || cdr instanceof AST_Unary && !unary_side_effects(cdr.operator)) { - field = "expression"; - } else if (cdr instanceof AST_Conditional) { - field = "condition"; - } else { - expressions[++i] = expressions[j]; - break; - } - parent = cdr; - cdr = cdr[field] = cdr[field].clone(); - } - } - end = i; - expressions.length = end + 1; - } }); AST_Unary.DEFMETHOD("lift_sequences", function(compressor){ @@ -4712,7 +4974,7 @@ merge(Compressor.prototype, { if (d.single_use && (is_func_expr(fixed) || fixed instanceof AST_ClassExpression)) { if (d.scope !== self.scope && (!compressor.option("reduce_funcs") && is_func_expr(fixed) - || d.escaped + || d.escaped == 1 || fixed.inlined)) { d.single_use = false; } else if (recursive_ref(compressor, d)) { @@ -4856,6 +5118,25 @@ merge(Compressor.prototype, { var ASSIGN_OPS = [ '+', '-', '/', '*', '%', '>>', '<<', '>>>', '|', '^', '&' ]; var ASSIGN_OPS_COMMUTATIVE = [ '*', '|', '^', '&' ]; OPT(AST_Assign, function(self, compressor){ + if (compressor.option("dead_code") + && self.left instanceof AST_SymbolRef + && self.left.definition().scope === compressor.find_parent(AST_Lambda)) { + var level = 0, node, parent = self; + do { + node = parent; + parent = compressor.parent(level++); + if (parent instanceof AST_Exit) { + if (in_try(level, parent instanceof AST_Throw)) break; + if (self.operator == "=") return self.right; + return make_node(AST_Binary, self, { + operator: self.operator.slice(0, -1), + left: self.left, + right: self.right + }).optimize(compressor); + } + } while (parent instanceof AST_Binary && parent.right === node + || parent instanceof AST_Sequence && parent.tail_node() === node); + } self = self.lift_sequences(compressor); if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) { // x = expr1 OP expr2 @@ -4876,6 +5157,17 @@ merge(Compressor.prototype, { } } return self; + + function in_try(level, no_catch) { + var scope = self.left.definition().scope; + var parent; + while ((parent = compressor.parent(level++)) !== scope) { + if (parent instanceof AST_Try) { + if (parent.bfinally) return true; + if (no_catch && parent.bcatch) return true; + } + } + } }); OPT(AST_DefaultAssign, function(self, compressor){ diff --git a/lib/output.js b/lib/output.js index 5b7aa7ea..b57eb3a8 100644 --- a/lib/output.js +++ b/lib/output.js @@ -52,6 +52,7 @@ function is_some_comments(comment) { function OutputStream(options) { + var readonly = !options; options = defaults(options, { ascii_only : false, beautify : false, @@ -133,11 +134,16 @@ function OutputStream(options) { } }); } : function(str) { - return str.replace(/[\ud800-\udbff](?![\udc00-\udfff])/g, function(ch) { - return "\\u" + ch.charCodeAt(0).toString(16); - }).replace(/(^|[^\ud800-\udbff])([\udc00-\udfff])/g, function(match, prefix, ch) { - return prefix + "\\u" + ch.charCodeAt(0).toString(16); - }); + var s = ""; + for (var i = 0, len = str.length; i < len; i++) { + if (is_surrogate_pair_head(str[i]) && !is_surrogate_pair_tail(str[i + 1]) + || is_surrogate_pair_tail(str[i]) && !is_surrogate_pair_head(str[i - 1])) { + s += "\\u" + str.charCodeAt(i).toString(16); + } else { + s += str[i]; + } + } + return s; }; function make_string(str, quote) { @@ -210,6 +216,9 @@ function OutputStream(options) { var might_need_space = false; var might_need_semicolon = false; var might_add_newline = 0; + var need_newline_indented = false; + var need_space = false; + var newline_insert = -1; var last = ""; var mapping_token, mapping_name, mappings = options.source_map && []; @@ -269,6 +278,21 @@ function OutputStream(options) { str = String(str); var ch = get_full_char(str, 0); var prev = get_full_char(last, last.length - 1); + if (need_newline_indented && ch) { + need_newline_indented = false; + if (ch != "\n") { + print("\n"); + indent(); + } + } + if (need_space && ch) { + need_space = false; + if (!/[\s;})]/.test(ch)) { + space(); + } + } + newline_insert = -1; + var prev = last.charAt(last.length - 1); if (might_need_semicolon) { might_need_semicolon = false; @@ -370,7 +394,13 @@ function OutputStream(options) { } : function(col, cont) { return cont() }; var newline = options.beautify ? function() { - print("\n"); + if (newline_insert < 0) return print("\n"); + if (OUTPUT[newline_insert] != "\n") { + OUTPUT = OUTPUT.slice(0, newline_insert) + "\n" + OUTPUT.slice(newline_insert); + current_pos++; + current_line++; + } + newline_insert++; } : options.max_line_len ? function() { ensure_line_len(); might_add_newline = OUTPUT.length; @@ -442,6 +472,118 @@ function OutputStream(options) { return OUTPUT; }; + function prepend_comments(node) { + var self = this; + var start = node.start; + if (!start) return; + if (!(start.comments_before && start.comments_before._dumped === self)) { + var comments = start.comments_before; + if (!comments) { + comments = start.comments_before = []; + } + comments._dumped = self; + + if (node instanceof AST_Exit && node.value) { + var tw = new TreeWalker(function(node) { + var parent = tw.parent(); + if (parent instanceof AST_Exit + || parent instanceof AST_Binary && parent.left === node + || parent.TYPE == "Call" && parent.expression === node + || parent instanceof AST_Conditional && parent.condition === node + || parent instanceof AST_Dot && parent.expression === node + || parent instanceof AST_Sequence && parent.expressions[0] === node + || parent instanceof AST_Sub && parent.expression === node + || parent instanceof AST_UnaryPostfix) { + if (!node.start) return; + var text = node.start.comments_before; + if (text && text._dumped !== self) { + text._dumped = self; + comments = comments.concat(text); + } + } else { + return true; + } + }); + tw.push(node); + node.value.walk(tw); + } + + if (current_pos == 0) { + if (comments.length > 0 && options.shebang && comments[0].type == "comment5") { + print("#!" + comments.shift().value + "\n"); + indent(); + } + var preamble = options.preamble; + if (preamble) { + print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n")); + } + } + + comments = comments.filter(comment_filter, node); + if (comments.length == 0) return; + var last_nlb = /(^|\n) *$/.test(OUTPUT); + comments.forEach(function(c, i) { + if (!last_nlb) { + if (c.nlb) { + print("\n"); + indent(); + last_nlb = true; + } else if (i > 0) { + space(); + } + } + if (/comment[134]/.test(c.type)) { + print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n"); + indent(); + last_nlb = true; + } else if (c.type == "comment2") { + print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/"); + last_nlb = false; + } + }); + if (!last_nlb) { + if (start.nlb) { + print("\n"); + indent(); + } else { + space(); + } + } + } + } + + function append_comments(node, tail) { + var self = this; + var token = node.end; + if (!token) return; + var comments = token[tail ? "comments_before" : "comments_after"]; + if (comments && comments._dumped !== self) { + comments._dumped = self; + var insert = OUTPUT.length; + comments.filter(comment_filter, node).forEach(function(c, i) { + need_space = false; + if (need_newline_indented) { + print("\n"); + indent(); + need_newline_indented = false; + } else if (c.nlb && (i > 0 || !/(^|\n) *$/.test(OUTPUT))) { + print("\n"); + indent(); + } else if (i > 0 || !tail) { + space(); + } + if (/comment[134]/.test(c.type)) { + print("//" + c.value.replace(/[@#]__PURE__/g, ' ')); + need_newline_indented = true; + } else if (c.type == "comment2") { + print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/"); + need_space = true; + } + }); + if (OUTPUT.length > insert) newline_insert = insert; + } + } + var stack = []; return { get : get, @@ -484,7 +626,8 @@ function OutputStream(options) { with_square : with_square, add_mapping : add_mapping, option : function(opt) { return options[opt] }, - comment_filter : comment_filter, + prepend_comments: readonly ? noop : prepend_comments, + append_comments : readonly ? noop : append_comments, line : function() { return current_line }, col : function() { return current_col }, pos : function() { return current_pos }, @@ -520,9 +663,10 @@ function OutputStream(options) { use_asm = active_scope; } function doit() { - self.add_comments(stream); + stream.prepend_comments(self); self.add_source_map(stream); generator(self, stream); + stream.append_comments(self); } stream.push_node(self); if (force_parens || self.needs_parens(stream)) { @@ -539,77 +683,10 @@ function OutputStream(options) { AST_Node.DEFMETHOD("print_to_string", function(options){ var s = OutputStream(options); - if (!options) s._readonly = true; this.print(s); return s.get(); }); - /* -----[ comments ]----- */ - - AST_Node.DEFMETHOD("add_comments", function(output){ - if (output._readonly) return; - var self = this; - var start = self.start; - if (start && !start._comments_dumped) { - start._comments_dumped = true; - var comments = start.comments_before || []; - - // XXX: ugly fix for https://github.com/mishoo/UglifyJS2/issues/112 - // and https://github.com/mishoo/UglifyJS2/issues/372 - if (self instanceof AST_Exit && self.value) { - self.value.walk(new TreeWalker(function(node){ - if (node.start && node.start.comments_before) { - comments = comments.concat(node.start.comments_before); - node.start.comments_before = []; - } - if (node instanceof AST_Function || - node instanceof AST_Array || - node instanceof AST_Object) - { - return true; // don't go inside. - } - })); - } - - if (output.pos() == 0) { - if (comments.length > 0 && output.option("shebang") && comments[0].type == "comment5") { - output.print("#!" + comments.shift().value + "\n"); - output.indent(); - } - var preamble = output.option("preamble"); - if (preamble) { - output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n")); - } - } - - comments = comments.filter(output.comment_filter, self); - - // Keep single line comments after nlb, after nlb - if (!output.option("beautify") && comments.length > 0 && - /comment[134]/.test(comments[0].type) && - output.col() !== 0 && comments[0].nlb) - { - output.print("\n"); - } - - comments.forEach(function(c){ - if (/comment[134]/.test(c.type)) { - output.print("//" + c.value + "\n"); - output.indent(); - } - else if (c.type == "comment2") { - output.print("/*" + c.value + "*/"); - if (start.nlb) { - output.print("\n"); - output.indent(); - } else { - output.space(); - } - } - }); - } - }); - /* -----[ PARENTHESES ]----- */ function PARENS(nodetype, func) { @@ -897,14 +974,21 @@ function OutputStream(options) { self.body.print(output); output.semicolon(); }); - function print_bracketed(body, output, allow_directives) { - if (body.length > 0) output.with_block(function(){ - display_body(body, false, output, allow_directives); - }); - else output.print("{}"); + function print_bracketed(self, output, allow_directives) { + if (self.body.length > 0) { + output.with_block(function() { + display_body(self.body, false, output, allow_directives); + }); + } else { + output.print("{"); + output.with_indent(output.next_indent(), function() { + output.append_comments(self, true); + }); + output.print("}"); + } }; DEFPRINT(AST_BlockStatement, function(self, output){ - print_bracketed(self.body, output); + print_bracketed(self, output); }); DEFPRINT(AST_EmptyStatement, function(self, output){ output.semicolon(); @@ -1016,7 +1100,7 @@ function OutputStream(options) { }); }); output.space(); - print_bracketed(self.body, output, true); + print_bracketed(self, output, true); }); DEFPRINT(AST_Lambda, function(self, output){ self._do_print(output); @@ -1071,7 +1155,7 @@ function OutputStream(options) { if (self.body instanceof AST_Node) { self.body.print(output); } else { - print_bracketed(self.body, output); + print_bracketed(self, output); } if (needs_parens) { output.print(")") } }); @@ -1228,7 +1312,7 @@ function OutputStream(options) { DEFPRINT(AST_Try, function(self, output){ output.print("try"); output.space(); - print_bracketed(self.body, output); + print_bracketed(self, output); if (self.bcatch) { output.space(); self.bcatch.print(output); @@ -1245,12 +1329,12 @@ function OutputStream(options) { self.argname.print(output); }); output.space(); - print_bracketed(self.body, output); + print_bracketed(self, output); }); DEFPRINT(AST_Finally, function(self, output){ output.print("finally"); output.space(); - print_bracketed(self.body, output); + print_bracketed(self, output); }); /* -----[ var/const ]----- */ diff --git a/lib/parse.js b/lib/parse.js index eebc4967..bfed3e97 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -365,11 +365,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } if (!is_comment) { ret.comments_before = S.comments_before; - S.comments_before = []; - // make note of any newlines in the comments that came before - for (var i = 0, len = ret.comments_before.length; i < len; i++) { - ret.nlb = ret.nlb || ret.comments_before[i].nlb; - } + ret.comments_after = S.comments_before = []; } S.newline_before = false; return new AST_Token(ret); @@ -2058,8 +2054,29 @@ function parse($TEXT, options) { }) : exprs.length == 1 ? exprs[0] : new AST_Sequence({ expressions: exprs }); + if (ex.start) { + var len = start.comments_before.length; + [].unshift.apply(ex.start.comments_before, start.comments_before); + start.comments_before = ex.start.comments_before; + start.comments_before_length = len; + if (len == 0 && start.comments_before.length > 0) { + var comment = start.comments_before[0]; + if (!comment.nlb) { + comment.nlb = start.nlb; + start.nlb = false; + } + } + start.comments_after = ex.start.comments_after; + } ex.start = start; - ex.end = S.token; + var end = prev(); + if (ex.end) { + end.comments_before = ex.end.comments_before; + [].push.apply(ex.end.comments_after, end.comments_after); + end.comments_after = ex.end.comments_after; + } + ex.end = end; + if (ex instanceof AST_Call) mark_pure(ex); return subscripts(ex, allow_calls); case "[": return subscripts(array_(), allow_calls); @@ -2625,6 +2642,19 @@ function parse($TEXT, options) { return sym; }; + function mark_pure(call) { + var start = call.start; + var comments = start.comments_before; + var i = HOP(start, "comments_before_length") ? start.comments_before_length : comments.length; + while (--i >= 0) { + var comment = comments[i]; + if (/[@#]__PURE__/.test(comment.value)) { + call.pure = comment; + break; + } + } + } + var subscripts = function(expr, allow_calls) { var start = expr.start; if (is("punc", ".")) { @@ -2649,12 +2679,14 @@ function parse($TEXT, options) { } if (allow_calls && is("punc", "(")) { next(); - return subscripts(new AST_Call({ + var call = new AST_Call({ start : start, expression : expr, args : call_args(), end : prev() - }), true); + }); + mark_pure(call); + return subscripts(call, true); } if (is("template_head")) { return subscripts(new AST_PrefixedTemplateString({ @@ -2736,7 +2768,8 @@ function parse($TEXT, options) { var op = is("operator") ? S.token.value : null; if (op == "in" && no_in) op = null; if (op == "**" && left instanceof AST_UnaryPrefix - && left.end === S.prev /* unary token in front not allowed, but allowed if prev is for example `)` */ + /* unary token in front not allowed - parenthesis required */ + && !is_token(left.start, "punc", "(") && left.operator !== "--" && left.operator !== "++") unexpected(left.start); var prec = op != null ? PRECEDENCE[op] : null; diff --git a/lib/transform.js b/lib/transform.js index a5a93026..edc58c7c 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -60,12 +60,9 @@ TreeTransformer.prototype = new TreeWalker; tw.push(this); if (tw.before) x = tw.before(this, descend, in_list); if (x === undefined) { - if (!tw.after) { - x = this; - descend(x, tw); - } else { - tw.stack[tw.stack.length - 1] = x = this; - descend(x, tw); + x = this; + descend(x, tw); + if (tw.after) { y = tw.after(x, in_list); if (y !== undefined) x = y; } diff --git a/lib/utils.js b/lib/utils.js index 76306919..dab7f566 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,10 +43,6 @@ "use strict"; -function slice(a, start) { - return Array.prototype.slice.call(a, start || 0); -}; - function characters(str) { return str.split(""); }; @@ -214,18 +210,6 @@ function mergeSort(array, cmp) { return _ms(array); }; -function set_difference(a, b) { - return a.filter(function(el){ - return b.indexOf(el) < 0; - }); -}; - -function set_intersection(a, b) { - return a.filter(function(el){ - return b.indexOf(el) >= 0; - }); -}; - // this function is taken from Acorn [1], written by Marijn Haverbeke // [1] https://github.com/marijnh/acorn function makePredicate(words) { @@ -340,7 +324,7 @@ function first_in_statement(stack) { if (p instanceof AST_Statement && p.body === node) return true; if ((p instanceof AST_Sequence && p.expressions[0] === node) || - (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || + (p.TYPE == "Call" && p.expression === node ) || (p instanceof AST_Dot && p.expression === node ) || (p instanceof AST_Sub && p.expression === node ) || (p instanceof AST_Conditional && p.condition === node ) || diff --git a/package.json b/package.json index d3bb8336..c83a7953 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.2.2", + "version": "3.3.0", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/asm.js b/test/compress/asm.js index 527e6b43..a9047c5d 100644 --- a/test/compress/asm.js +++ b/test/compress/asm.js @@ -16,7 +16,6 @@ asm_mixed: { hoist_vars : true, if_return : true, join_vars : true, - cascade : true, side_effects : true, negate_iife : true }; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 51c81798..f2ef18df 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -2,7 +2,7 @@ collapse_vars_side_effects_1: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -83,7 +83,7 @@ collapse_vars_side_effects_2: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function fn(x) { return console.log(x), x; } @@ -151,8 +151,8 @@ collapse_vars_issue_721: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, - reduce_funcs: true, reduce_vars:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, + reduce_funcs: true, reduce_vars:true, passes:2 } input: { define(["require", "exports", 'handlebars'], function (require, exports, hb) { @@ -218,7 +218,7 @@ collapse_vars_properties: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -246,7 +246,7 @@ collapse_vars_if: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -297,7 +297,7 @@ collapse_vars_while: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:false, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -346,7 +346,7 @@ collapse_vars_do_while: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:false, loops:false, unused:"keep_assign", - hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true, + hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { @@ -422,7 +422,7 @@ collapse_vars_do_while_drop_assign: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:false, loops:false, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1(y) { @@ -497,7 +497,7 @@ collapse_vars_seq: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { var f1 = function(x, y) { @@ -505,20 +505,23 @@ collapse_vars_seq: { a = z, b = 7; return a + b; }; + console.log(f1(1, 2)); } expect: { var f1 = function(x, y) { - var a, b, r = x + y; - return a = r * r - r, b = 7, a + b + var r = x + y; + return r * r - r + 7; }; + console.log(f1(1, 2)); } + expect_stdout: "13" } collapse_vars_throw: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { var f1 = function(x, y) { @@ -526,20 +529,31 @@ collapse_vars_throw: { a = z, b = 7; throw a + b; }; + try { + f1(1, 2); + } catch (e) { + console.log(e); + } } expect: { var f1 = function(x, y) { - var a, b, r = x + y; - throw a = r * r - r, b = 7, a + b + var r = x + y; + throw r * r - r + 7; }; + try { + f1(1, 2); + } catch (e) { + console.log(e); + } } + expect_stdout: "13" } collapse_vars_switch: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1() { @@ -577,9 +591,9 @@ collapse_vars_switch: { collapse_vars_assignment: { options = { - collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + collapse_vars:true, sequences:true, properties:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function log(x) { return console.log(x), x; } @@ -625,7 +639,7 @@ collapse_vars_assignment: { return a = a; } function f1(c) { - return 1 - 3 / c + return 1 - 3 / c; } function f2(c) { return log(c = 3 / c - 7); @@ -650,9 +664,9 @@ collapse_vars_assignment: { collapse_vars_lvalues: { options = { - collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + collapse_vars:true, sequences:true, properties:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:"keep_assign", - hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true, + hoist_funs:true, keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { @@ -683,9 +697,9 @@ collapse_vars_lvalues: { collapse_vars_lvalues_drop_assign: { options = { - collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + collapse_vars:true, sequences:true, properties:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, passes:3 } input: { function f0(x) { var i = ++x; return x += i; } @@ -706,18 +720,18 @@ collapse_vars_lvalues_drop_assign: { function f3(x) { var a = (x -= 3); return x + a; } function f4(x) { var a = (x -= 3); return x + a; } function f5(x) { e1(), e2(); var c = --x; return x - c; } - function f6(x) { e1(), e2(); return --x - x; } - function f7(x) { e1(); return x - (e2() - x); } - function f8(x) { e1(); return x - (e2() - x); } - function f9(x) { e1(); return e2() - x - x; } + function f6(x) { return e1(), e2(), --x - x; } + function f7(x) { return e1(), x - (e2() - x); } + function f8(x) { return e1(), x - (e2() - x); } + function f9(x) { return e1(), e2() - x - x; } } } collapse_vars_misc1: { options = { - collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + collapse_vars:true, sequences:true, properties:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -765,7 +779,7 @@ collapse_vars_self_reference: { collapse_vars:true, unused:false, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { // avoid bug in self-referential declaration. @@ -795,7 +809,7 @@ collapse_vars_repeated: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -838,7 +852,7 @@ collapse_vars_closures: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -866,7 +880,7 @@ collapse_vars_unary: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f0(o, p) { @@ -929,7 +943,7 @@ collapse_vars_try: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -985,7 +999,7 @@ collapse_vars_array: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1(x, y) { @@ -1019,7 +1033,7 @@ collapse_vars_object: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f0(x, y) { @@ -1087,7 +1101,7 @@ collapse_vars_eval_and_with: { options = { collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { // Don't attempt to collapse vars in presence of eval() or with statement. @@ -1127,7 +1141,7 @@ collapse_vars_constants: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, reduce_funcs: true, reduce_vars:true } input: { @@ -1165,7 +1179,7 @@ collapse_vars_arguments: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + keep_fargs:true, if_return:true, join_vars:true, side_effects:true, toplevel:true, reduce_funcs: true, reduce_vars:true } input: { @@ -1188,7 +1202,7 @@ collapse_vars_short_circuit: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f0(x) { var a = foo(), b = bar(); return b || x; } @@ -1241,7 +1255,6 @@ collapse_vars_short_circuited_conditions: { keep_fargs: true, if_return: false, join_vars: true, - cascade: true, side_effects: true, } input: { @@ -1279,7 +1292,6 @@ collapse_vars_short_circuited_conditions: { collapse_vars_regexp: { options = { booleans: true, - cascade: true, collapse_vars: true, comparisons: true, conditionals: true, @@ -1542,7 +1554,6 @@ issue_1605_2: { issue_1631_1: { options = { - cascade: true, collapse_vars: true, hoist_funs: true, join_vars: true, @@ -1578,7 +1589,6 @@ issue_1631_1: { issue_1631_2: { options = { - cascade: true, collapse_vars: true, hoist_funs: true, join_vars: true, @@ -1614,7 +1624,6 @@ issue_1631_2: { issue_1631_3: { options = { - cascade: true, collapse_vars: true, hoist_funs: true, join_vars: true, @@ -1789,7 +1798,7 @@ var_defs: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { var f1 = function(x, y) { @@ -1847,7 +1856,7 @@ for_init: { } } -switch_case: { +switch_case_1: { options = { collapse_vars: true, unused: true, @@ -1866,16 +1875,71 @@ switch_case: { } expect: { function f(x, y, z) { - var c = z; switch (x()) { default: d(); case y(): e(); - case c: f(); + case z: f(); } } } } +switch_case_2: { + options = { + collapse_vars: true, + } + input: { + var a = 1, b = 2; + switch (b++) { + case b: + var c = a; + var a; + break; + } + console.log(a); + } + expect: { + var a = 1, b = 2; + switch (b++) { + case b: + var c = a; + var a; + break; + } + console.log(a); + } + expect_stdout: "1" +} + +switch_case_3: { + options = { + collapse_vars: true, + } + input: { + var a = 1, b = 2; + switch (a) { + case a: + var b; + break; + case b: + break; + } + console.log(b); + } + expect: { + var a = 1, b = 2; + switch (a) { + case a: + var b; + break; + case b: + break; + } + console.log(b); + } + expect_stdout: "2" +} + issue_27: { options = { collapse_vars: true, @@ -2028,10 +2092,8 @@ undeclared: { } expect: { function f(x, y) { - var a; - a = x; b = y; - return b + a; + return b + x; } } } @@ -3557,15 +3619,14 @@ issue_2436_4: { }(o)); } expect: { - console.log(function(c) { - return { - x: c.a, - y: c.b, - }; - }({ + console.log({ + x: (c = { a: 1, b: 2, - })); + }).a, + y: c.b, + }); + var c; } expect_stdout: true } @@ -3688,12 +3749,11 @@ issue_2436_8: { }(o)); } expect: { - console.log(function(c) { - return { - x: c.a, - y: c.b, - }; - }(o)); + console.log({ + x: (c = o).a, + y: c.b, + }); + var c; } expect_stdout: true } @@ -3718,12 +3778,11 @@ issue_2436_9: { } expect: { var o = console; - console.log(function(c) { - return { - x: c.a, - y: c.b, - }; - }(o)); + console.log({ + x: (c = o).a, + y: c.b, + }); + var c; } expect_stdout: true } @@ -3763,13 +3822,12 @@ issue_2436_10: { o = { b: 3 }; return n; } - console.log(function(c) { - return [ - c.a, - f(c.b), - c.b, - ]; - }(o).join(" ")); + console.log((c = o, [ + c.a, + f(c.b), + c.b, + ]).join(" ")); + var c; } expect_stdout: "1 2 2" } @@ -3977,3 +4035,282 @@ issue_2506: { } expect_stdout: "1" } + +issue_2571_1: { + options = { + collapse_vars: true, + toplevel: true, + } + input: { + var b = 1; + try { + var a = function f0(c) { + throw c; + }(2); + var d = --b + a; + } catch (e) { + } + console.log(b); + } + expect: { + var b = 1; + try { + var a = function f0(c) { + throw c; + }(2); + var d = --b + a; + } catch (e) { + } + console.log(b); + } + expect_stdout: "1" +} + +issue_2571_2: { + options = { + collapse_vars: true, + toplevel: true, + } + input: { + try { + var a = A, b = 1; + throw a; + } catch (e) { + console.log(b); + } + } + expect: { + try { + var a = A, b = 1; + throw a; + } catch (e) { + console.log(b); + } + } + expect_stdout: "undefined" +} + +may_throw_1: { + options = { + collapse_vars: true, + } + input: { + function f() { + var a_2 = function() { + var a; + }(); + } + } + expect: { + function f() { + var a_2 = function() { + var a; + }(); + } + } +} + +may_throw_2: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function f(b) { + try { + var a = x(); + ++b; + return b(a); + } catch(e) {} + console.log(b); + } + f(0); + } + expect: { + function f(b) { + try { + var a = x(); + return (++b)(a); + } catch(e) {} + console.log(b); + } + f(0); + } + expect_stdout: "0" +} + +side_effect_free_replacement: { + options = { + collapse_vars: true, + inline: true, + side_effects: true, + unused: true, + } + input: { + var b; + (function(a) { + x(a); + })(b); + } + expect: { + var b; + x(b); + } +} + +recursive_function_replacement: { + rename = true + options = { + collapse_vars: true, + inline: true, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + mangle = {} + input: { + function f(a) { + return x(g(a)); + } + function g(a) { + return y(f(a)); + } + console.log(f(c)); + } + expect: { + function f(n) { + return x(y(f(n))); + } + console.log(f(c)); + } +} + +cascade_conditional: { + options = { + collapse_vars: true, + } + input: { + function f(a, b) { + (a = x(), a) ? a++ : (b = y(a), b(a)); + } + } + expect: { + function f(a, b) { + (a = x()) ? a++ : (b = y(a))(a); + } + } +} + +cascade_if_1: { + options = { + collapse_vars: true, + } + input: { + var a; + if (a = x(), a) + if (a == y()) z(); + } + expect: { + var a; + if (a = x()) + if (a == y()) z(); + } +} + +cascade_if_2: { + options = { + collapse_vars: true, + } + input: { + function f(a, b) { + if (a(), b = x()) return b; + } + } + expect: { + function f(a, b) { + if (a(), b = x()) return b; + } + } +} + +cascade_return: { + options = { + collapse_vars: true, + } + input: { + function f(a) { + return a = x(); + return a; + } + } + expect: { + function f(a) { + return a = x(); + return a; + } + } +} + +cascade_switch: { + options = { + collapse_vars: true, + } + input: { + function f(a, b) { + switch(a = x(), a) { + case a = x(), b(a): + break; + } + } + } + expect: { + function f(a, b) { + switch(a = x()) { + case b(a = x()): + break; + } + } + } +} + +cascade_call: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function f(a) { + var b; + return x((b = a, y(b))); + } + } + expect: { + function f(a) { + return x(y(a)); + } + } +} + +replace_all_var: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = "PASS"; + (function() { + var b = b || c && c[a = "FAIL"], c = a; + })(); + console.log(a); + } + expect: { + var a = "PASS"; + (function() { + var b = b || c && c[a = "FAIL"], c = a; + })(); + console.log(a); + } + expect_stdout: "PASS" +} diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 143ece4a..4d61d39f 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -649,7 +649,7 @@ ternary_boolean_consequent: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1() { return a == b ? true : x; } @@ -677,7 +677,7 @@ ternary_boolean_alternative: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1() { return a == b ? x : true; } diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index f0de31a6..c25fe1ce 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -636,3 +636,414 @@ issue_2383_3: { expect_stdout: "undefined undefined 8 undefined 7" node_version: ">=6" } + +collapse_vars_assignment: { + options = { + collapse_vars: true, + dead_code: true, + passes: 2, + unused: true, + } + input: { + function f0(c) { + var a = 3 / c; + return a = a; + } + } + expect: { + function f0(c) { + return 3 / c; + } + } +} + +collapse_vars_lvalues_drop_assign: { + options = { + collapse_vars: true, + dead_code: true, + unused: true, + } + input: { + function f0(x) { var i = ++x; return x += i; } + function f1(x) { var a = (x -= 3); return x += a; } + function f2(x) { var z = x, a = ++z; return z += a; } + } + expect: { + function f0(x) { var i = ++x; return x + i; } + function f1(x) { var a = (x -= 3); return x + a; } + function f2(x) { var z = x, a = ++z; return z + a; } + } +} + +collapse_vars_misc1: { + options = { + collapse_vars: true, + dead_code: true, + unused: true, + } + input: { + function f10(x) { var a = 5, b = 3; return a += b; } + function f11(x) { var a = 5, b = 3; return a += --b; } + } + expect: { + function f10(x) { return 5 + 3; } + function f11(x) { var b = 3; return 5 + --b; } + } +} + +return_assignment: { + options = { + dead_code: true, + unused: true, + } + input: { + function f1(a, b, c) { + return a = x(), b = y(), b = a && (c >>= 5); + } + function f2() { + return e = x(); + } + function f3(e) { + return e = x(); + } + function f4() { + var e; + return e = x(); + } + function f5(a) { + try { + return a = x(); + } catch (b) { + console.log(a); + } + } + function f6(a) { + try { + return a = x(); + } finally { + console.log(a); + } + } + function y() { + console.log("y"); + } + function test(inc) { + var counter = 0; + x = function() { + counter += inc; + if (inc < 0) throw counter; + return counter; + }; + [ f1, f2, f3, f4, f5, f6 ].forEach(function(f, i) { + e = null; + try { + i += 1; + console.log("result " + f(10 * i, 100 * i, 1000 * i)); + } catch (x) { + console.log("caught " + x); + } + if (null !== e) console.log("e: " + e); + }); + } + var x, e; + test(1); + test(-1); + } + expect: { + function f1(a, b, c) { + return a = x(), y(), a && (c >> 5); + } + function f2() { + return e = x(); + } + function f3(e) { + return x(); + } + function f4() { + return x(); + } + function f5(a) { + try { + return x(); + } catch (b) { + console.log(a); + } + } + function f6(a) { + try { + return a = x(); + } finally { + console.log(a); + } + } + function y() { + console.log("y"); + } + function test(inc) { + var counter = 0; + x = function() { + counter += inc; + if (inc < 0) throw counter; + return counter; + }; + [ f1, f2, f3, f4, f5, f6 ].forEach(function(f, i) { + e = null; + try { + i += 1; + console.log("result " + f(10 * i, 100 * i, 1000 * i)); + } catch (x) { + console.log("caught " + x); + } + if (null !== e) console.log("e: " + e); + }); + } + var x, e; + test(1); + test(-1); + } + expect_stdout: [ + "y", + "result 31", + "result 2", + "e: 2", + "result 3", + "result 4", + "result 5", + "6", + "result 6", + "caught -1", + "caught -2", + "caught -3", + "caught -4", + "50", + "result undefined", + "60", + "caught -6", + ] +} + +throw_assignment: { + options = { + dead_code: true, + unused: true, + } + input: { + function f1() { + throw a = x(); + } + function f2(a) { + throw a = x(); + } + function f3() { + var a; + throw a = x(); + } + function f4() { + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f5(a) { + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f6() { + var a; + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f7() { + try { + throw a = x(); + } finally { + console.log(a); + } + } + function f8(a) { + try { + throw a = x(); + } finally { + console.log(a); + } + } + function f9() { + var a; + try { + throw a = x(); + } finally { + console.log(a); + } + } + function test(inc) { + var counter = 0; + x = function() { + counter += inc; + if (inc < 0) throw counter; + return counter; + }; + [ f1, f2, f3, f4, f5, f6, f7, f8, f9 ].forEach(function(f, i) { + a = null; + try { + f(10 * (1 + i)); + } catch (x) { + console.log("caught " + x); + } + if (null !== a) console.log("a: " + a); + }); + } + var x, a; + test(1); + test(-1); + } + expect: { + function f1() { + throw a = x(); + } + function f2(a) { + throw x(); + } + function f3() { + throw x(); + } + function f4() { + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f5(a) { + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f6() { + var a; + try { + throw a = x(); + } catch (b) { + console.log(a); + } + } + function f7() { + try { + throw a = x(); + } finally { + console.log(a); + } + } + function f8(a) { + try { + throw a = x(); + } finally { + console.log(a); + } + } + function f9() { + var a; + try { + throw a = x(); + } finally { + console.log(a); + } + } + function test(inc) { + var counter = 0; + x = function() { + counter += inc; + if (inc < 0) throw counter; + return counter; + }; + [ f1, f2, f3, f4, f5, f6, f7, f8, f9 ].forEach(function(f, i) { + a = null; + try { + f(10 * (1 + i)); + } catch (x) { + console.log("caught " + x); + } + if (null !== a) console.log("a: " + a); + }); + } + var x, a; + test(1); + test(-1); + } + expect_stdout: [ + "caught 1", + "a: 1", + "caught 2", + "caught 3", + "4", + "a: 4", + "5", + "6", + "7", + "caught 7", + "a: 7", + "8", + "caught 8", + "9", + "caught 9", + "caught -1", + "caught -2", + "caught -3", + "null", + "50", + "undefined", + "null", + "caught -7", + "80", + "caught -8", + "undefined", + "caught -9", + ] +} + +issue_2597: { + options = { + dead_code: true, + } + input: { + function f(b) { + try { + try { + throw "foo"; + } catch (e) { + return b = true; + } + } finally { + b && (a = "PASS"); + } + } + var a = "FAIL"; + f(); + console.log(a); + } + expect: { + function f(b) { + try { + try { + throw "foo"; + } catch (e) { + return b = true; + } + } finally { + b && (a = "PASS"); + } + } + var a = "FAIL"; + f(); + console.log(a); + } + expect_stdout: "PASS" +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index c9cee798..92c3eef9 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -798,7 +798,7 @@ const_assign: { issue_1539: { options = { - cascade: true, + collapse_vars: true, sequences: true, side_effects: true, unused: true, @@ -845,7 +845,7 @@ vardef_value: { assign_binding: { options = { - cascade: true, + collapse_vars: true, side_effects: true, unused: true, } @@ -1548,7 +1548,7 @@ issue_2226_1: { issue_2226_2: { options = { - cascade: true, + collapse_vars: true, sequences: true, side_effects: true, unused: true, @@ -1561,8 +1561,8 @@ issue_2226_2: { } expect: { console.log(function(a, b) { - return a += b; - }(1, 2)); + return a += 2; + }(1)); } expect_stdout: "3" } diff --git a/test/compress/functions.js b/test/compress/functions.js index 563507f6..478ee561 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -21,7 +21,7 @@ iifes_returning_constants_keep_fargs_true: { join_vars : true, reduce_funcs : true, reduce_vars : true, - cascade : true, + collapse_vars : true, inline : true, } input: { @@ -58,7 +58,7 @@ iifes_returning_constants_keep_fargs_false: { join_vars : true, reduce_funcs : true, reduce_vars : true, - cascade : true, + collapse_vars : true, inline : true, } input: { @@ -423,9 +423,9 @@ inner_ref: { issue_2107: { options = { - cascade: true, collapse_vars: true, inline: true, + passes: 3, sequences: true, side_effects: true, unused: true, @@ -578,11 +578,10 @@ issue_2531_1: { } expect: { function outer() { - return function(value) { - return function() { - return value; - }; - }("Hello"); + return value = "Hello", function() { + return value; + }; + var value; } console.log("Greeting:", outer()()); } @@ -593,9 +592,10 @@ issue_2531_2: { options = { evaluate: true, inline: true, - passes: 2, + passes: 3, reduce_funcs: true, reduce_vars: true, + side_effects: true, unused: true, } input: { @@ -627,9 +627,10 @@ issue_2531_3: { options = { evaluate: true, inline: true, - passes: 2, + passes: 3, reduce_funcs: true, reduce_vars: true, + side_effects: true, toplevel: true, unused: true, } @@ -672,3 +673,753 @@ empty_body: { } } } + +inline_loop_1: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function f() { + return x(); + } + for (;;) f(); + } + expect: { + for (;;) x(); + } +} + +inline_loop_2: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (;;) f(); + function f() { + return x(); + } + } + expect: { + for (;;) x(); + } +} + +inline_loop_3: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var f = function() { + return x(); + }; + for (;;) f(); + } + expect: { + for (;;) x(); + } +} + +inline_loop_4: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (;;) f(); + var f = function() { + return x(); + }; + } + expect: { + for (;;) f(); + var f = function() { + return x(); + }; + } +} + +issue_2476: { + options = { + inline: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function foo(x, y, z) { + return x < y ? x * y + z : x * z - y; + } + for (var sum = 0, i = 0; i < 10; i++) + sum += foo(i, i + 1, 3 * i); + console.log(sum); + } + expect: { + for (var sum = 0, i = 0; i < 10; i++) + sum += (x = i, y = i + 1, z = 3 * i, x < y ? x * y + z : x * z - y); + var x, y, z; + console.log(sum); + } + expect_stdout: "465" +} + +issue_2601_1: { + options = { + inline: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var a = "FAIL"; + (function() { + function f(b) { + function g(b) { + b && b(); + } + g(); + (function() { + b && (a = "PASS"); + })(); + } + f("foo"); + })(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function() { + b = "foo", + function(b) { + b && b(); + }(), + b && (a = "PASS"); + var b; + })(), + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2601_2: { + rename = true + options = { + evaluate: true, + inline: true, + passes: 3, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + mangle = {} + input: { + var a = "FAIL"; + (function() { + function f(b) { + function g(b) { + b && b(); + } + g(); + (function() { + b && (a = "PASS"); + })(); + } + f("foo"); + })(); + console.log(a); + } + expect: { + var a = "FAIL"; + a = "PASS", + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2604_1: { + options = { + inline: true, + side_effects: true, + unused: true, + } + input: { + var a = "FAIL"; + (function() { + try { + throw 1; + } catch (b) { + (function f(b) { + b && b(); + })(); + b && (a = "PASS"); + } + })(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function() { + try { + throw 1; + } catch (b) { + (function(b) { + b && b(); + })(); + b && (a = "PASS"); + } + })(); + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2604_2: { + rename = true + options = { + evaluate: true, + inline: true, + passes: 3, + reduce_vars: true, + side_effects: true, + unused: true, + } + mangle = {} + input: { + var a = "FAIL"; + (function() { + try { + throw 1; + } catch (b) { + (function f(b) { + b && b(); + })(); + b && (a = "PASS"); + } + })(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function() { + try { + throw 1; + } catch (o) { + o && (a = "PASS"); + } + })(); + console.log(a); + } + expect_stdout: "PASS" +} + +unsafe_apply_1: { + options = { + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unsafe: true, + unused: true, + } + input: { + (function(a, b) { + console.log(a, b); + }).apply("foo", [ "bar" ]); + (function(a, b) { + console.log(this, a, b); + }).apply("foo", [ "bar" ]); + (function(a, b) { + console.log(a, b); + }).apply("foo", [ "bar" ], "baz"); + } + expect: { + console.log("bar", void 0); + (function(a, b) { + console.log(this, a, b); + }).call("foo", "bar"); + (function(a, b) { + console.log(a, b); + }).apply("foo", [ "bar" ], "baz"); + } + expect_stdout: true +} + +unsafe_apply_2: { + options = { + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + } + input: { + function foo() { + console.log(a, b); + } + var bar = function(a, b) { + console.log(this, a, b); + } + (function() { + foo.apply("foo", [ "bar" ]); + bar.apply("foo", [ "bar" ]); + })(); + } + expect: { + function foo() { + console.log(a, b); + } + var bar = function(a, b) { + console.log(this, a, b); + } + (function() { + foo("bar"); + bar.call("foo", "bar"); + })(); + } + expect_stdout: true +} + +unsafe_call_1: { + options = { + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unsafe: true, + unused: true, + } + input: { + (function(a, b) { + console.log(a, b); + }).call("foo", "bar"); + (function(a, b) { + console.log(this, a, b); + }).call("foo", "bar"); + } + expect: { + console.log("bar", void 0); + (function(a, b) { + console.log(this, a, b); + }).call("foo", "bar"); + } + expect_stdout: true +} + +unsafe_call_2: { + options = { + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + } + input: { + function foo() { + console.log(a, b); + } + var bar = function(a, b) { + console.log(this, a, b); + } + (function() { + foo.call("foo", "bar"); + bar.call("foo", "bar"); + })(); + } + expect: { + function foo() { + console.log(a, b); + } + var bar = function(a, b) { + console.log(this, a, b); + } + (function() { + foo("bar"); + bar.call("foo", "bar"); + })(); + } + expect_stdout: true +} + +unsafe_call_3: { + options = { + side_effects: true, + unsafe: true, + } + input: { + console.log(function() { + return arguments[0] + eval("arguments")[1]; + }.call(0, 1, 2)); + } + expect: { + console.log(function() { + return arguments[0] + eval("arguments")[1]; + }(1, 2)); + } + expect_stdout: "3" +} + +issue_2616: { + options = { + evaluate: true, + inline: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + function f() { + function g(NaN) { + (true << NaN) - 0/0 || (c = "PASS"); + } + g([]); + } + f(); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + (function() { + !function(NaN) { + (true << NaN) - 0/0 || (c = "PASS"); + }([]); + })(); + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2620_1: { + options = { + inline: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + function f(a) { + var b = function g(a) { + a && a(); + }(); + if (a) { + var d = c = "PASS"; + } + } + f(1); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + (function() { + (function(a) { + if (function(a) { + a && a(); + }(), a) c = "PASS"; + })(1); + })(), + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2620_2: { + options = { + conditionals: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + function f(a) { + var b = function g(a) { + a && a(); + }(); + if (a) { + var d = c = "PASS"; + } + } + f(1); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + c = "PASS", + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2620_3: { + options = { + evaluate: true, + inline: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + function f(a, NaN) { + function g() { + switch (a) { + case a: + break; + case c = "PASS", NaN: + break; + } + } + g(); + } + f(0/0); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + (function() { + (function(a, NaN) { + (function() { + switch (a) { + case a: + break; + case c = "PASS", NaN: + break; + } + })(); + })(NaN); + })(); + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2620_4: { + rename = true, + options = { + evaluate: true, + dead_code: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + switches: true, + unused: true, + } + input: { + var c = "FAIL"; + (function() { + function f(a, NaN) { + function g() { + switch (a) { + case a: + break; + case c = "PASS", NaN: + break; + } + } + g(); + } + f(0/0); + })(); + console.log(c); + } + expect: { + var c = "FAIL"; + !function() { + switch (NaN) { + case void (c = "PASS"): + } + }(); + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2630_1: { + options = { + collapse_vars: true, + inline: true, + passes: 2, + reduce_funcs: true, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = 0; + (function() { + while (f()); + function f() { + var a = function() { + var b = c++, d = c = 1 + c; + }(); + } + })(); + console.log(c); + } + expect: { + var c = 0; + (function() { + while (c++, void (c = 1 + c)); + })(), + console.log(c); + } + expect_stdout: "2" +} + +issue_2630_2: { + options = { + collapse_vars: true, + inline: true, + passes: 2, + reduce_vars: true, + sequences: true, + unused: true, + } + input: { + var c = 0; + !function() { + while (f()) {} + function f() { + var not_used = function() { + c = 1 + c; + }(c = c + 1); + } + }(); + console.log(c); + } + expect: { + var c = 0; + !function() { + while (c += 1, void (c = 1 + c)); + }(), console.log(c); + } + expect_stdout: "2" +} + +issue_2630_3: { + options = { + inline: true, + reduce_vars: true, + unused: true, + } + input: { + var x = 2, a = 1; + (function() { + function f1(a) { + f2(); + --x >= 0 && f1({}); + } + f1(a++); + function f2() { + a++; + } + })(); + console.log(a); + } + expect: { + var x = 2, a = 1; + (function() { + function f1(a) { + f2(); + --x >= 0 && f1({}); + } + f1(a++); + function f2() { + a++; + } + })(); + console.log(a); + } + expect_stdout: "5" +} + +issue_2630_4: { + options = { + collapse_vars: true, + inline: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + var x = 3, a = 1, b = 2; + (function() { + (function f1() { + while (--x >= 0 && f2()); + }()); + function f2() { + a++ + (b += a); + } + })(); + console.log(a); + } + expect: { + var x = 3, a = 1, b = 2; + (function() { + (function() { + while (--x >= 0 && void (a++, b += a)); + })(); + })(); + console.log(a); + } + expect_stdout: "2" +} + +issue_2630_5: { + options = { + collapse_vars: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + var c = 1; + !function() { + do { + c *= 10; + } while (f()); + function f() { + return function() { + return (c = 2 + c) < 100; + }(c = c + 3); + } + }(); + console.log(c); + } + expect: { + var c = 1; + !function() { + do { + c *= 10; + } while (c += 3, (c = 2 + c) < 100); + }(); + console.log(c); + } + expect_stdout: "155" +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index 8e94f9aa..b1f7c6ad 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -55,9 +55,8 @@ issue_2377_2: { console.log(obj.foo, obj.cube(3)); } expect: { - console.log(1, function(x) { - return x * x * x; - }(3)); + console.log(1, (x = 3, x * x * x)); + var x; } expect_stdout: "1 27" } @@ -67,9 +66,10 @@ issue_2377_3: { evaluate: true, inline: true, hoist_props: true, - passes: 3, + passes: 4, reduce_funcs: true, reduce_vars: true, + side_effects: true, toplevel: true, unused: true, } diff --git a/test/compress/issue-1034.js b/test/compress/issue-1034.js index f312408c..860a597f 100644 --- a/test/compress/issue-1034.js +++ b/test/compress/issue-1034.js @@ -2,7 +2,7 @@ non_hoisted_function_after_return: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true + if_return: true, join_vars: true, side_effects: true } input: { function foo(x) { @@ -38,7 +38,7 @@ non_hoisted_function_after_return_2a: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true, + if_return: true, join_vars: true, side_effects: true, collapse_vars: false, passes: 2, warnings: "verbose" } input: { @@ -85,7 +85,7 @@ non_hoisted_function_after_return_2b: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true, + if_return: true, join_vars: true, side_effects: true, collapse_vars: false } input: { @@ -123,7 +123,7 @@ non_hoisted_function_after_return_strict: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true + if_return: true, join_vars: true, side_effects: true } input: { "use strict"; @@ -164,7 +164,7 @@ non_hoisted_function_after_return_2a_strict: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true, + if_return: true, join_vars: true, side_effects: true, collapse_vars: false, passes: 2, warnings: "verbose" } input: { @@ -216,7 +216,7 @@ non_hoisted_function_after_return_2b_strict: { options = { hoist_funs: false, dead_code: true, conditionals: true, comparisons: true, evaluate: true, booleans: true, loops: true, unused: true, keep_fargs: true, - if_return: true, join_vars: true, cascade: true, side_effects: true, + if_return: true, join_vars: true, side_effects: true, collapse_vars: false } input: { diff --git a/test/compress/issue-1105.js b/test/compress/issue-1105.js index ea957930..151ca810 100644 --- a/test/compress/issue-1105.js +++ b/test/compress/issue-1105.js @@ -190,7 +190,6 @@ assorted_Infinity_NaN_undefined_in_with_scope: { keep_fargs: true, if_return: true, join_vars: true, - cascade: true, side_effects: true, sequences: false, keep_infinity: false, @@ -253,7 +252,6 @@ assorted_Infinity_NaN_undefined_in_with_scope_keep_infinity: { keep_fargs: true, if_return: true, join_vars: true, - cascade: true, side_effects: true, sequences: false, keep_infinity: true, diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index 994a00b3..9f4f466f 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -8,7 +8,6 @@ pure_function_calls: { unused : true, if_return : true, join_vars : true, - cascade : true, negate_iife : true, } input: { @@ -49,13 +48,13 @@ pure_function_calls: { a.b(), f.g(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:17,8]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:17,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:30,37]", - "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:30,16]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:28,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:38,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:39,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:16,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:16,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:29,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:29,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:27,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:37,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:38,31]", ] } @@ -69,7 +68,6 @@ pure_function_calls_toplevel: { unused : true, if_return : true, join_vars : true, - cascade : true, negate_iife : true, toplevel : true, } @@ -112,17 +110,17 @@ pure_function_calls_toplevel: { a.b(), f.g(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:79,8]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:79,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:92,37]", - "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:92,16]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:90,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:107,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:108,31]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:84,33]", - "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:84,12]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:100,45]", - "WARN: Dropping unused variable MyClass [test/compress/issue-1261.js:100,12]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:77,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:77,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:90,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:90,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:88,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:105,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:106,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:82,33]", + "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:82,12]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:98,45]", + "WARN: Dropping unused variable MyClass [test/compress/issue-1261.js:98,12]", ] } @@ -157,29 +155,29 @@ should_warn: { baz(); } expect_warnings: [ - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:137,61]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:137,23]", - "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:137,23]", - "WARN: Boolean || always true [test/compress/issue-1261.js:138,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,61]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:135,23]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:135,23]", + "WARN: Boolean || always true [test/compress/issue-1261.js:136,23]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:136,23]", + "WARN: Condition always true [test/compress/issue-1261.js:136,23]", + "WARN: Condition left of || always true [test/compress/issue-1261.js:137,8]", + "WARN: Condition always true [test/compress/issue-1261.js:137,8]", + "WARN: Boolean && always false [test/compress/issue-1261.js:138,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:138,23]", - "WARN: Condition always true [test/compress/issue-1261.js:138,23]", - "WARN: Condition left of || always true [test/compress/issue-1261.js:139,8]", - "WARN: Condition always true [test/compress/issue-1261.js:139,8]", - "WARN: Boolean && always false [test/compress/issue-1261.js:140,23]", + "WARN: Condition always false [test/compress/issue-1261.js:138,23]", + "WARN: Condition left of && always false [test/compress/issue-1261.js:139,8]", + "WARN: Condition always false [test/compress/issue-1261.js:139,8]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:140,23]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:140,23]", - "WARN: Condition always false [test/compress/issue-1261.js:140,23]", - "WARN: Condition left of && always false [test/compress/issue-1261.js:141,8]", - "WARN: Condition always false [test/compress/issue-1261.js:141,8]", - "WARN: + in boolean context always true [test/compress/issue-1261.js:142,23]", + "WARN: Condition always true [test/compress/issue-1261.js:140,23]", + "WARN: + in boolean context always true [test/compress/issue-1261.js:141,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:141,31]", + "WARN: Condition always true [test/compress/issue-1261.js:141,8]", "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:142,23]", - "WARN: Condition always true [test/compress/issue-1261.js:142,23]", - "WARN: + in boolean context always true [test/compress/issue-1261.js:143,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:143,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:143,24]", "WARN: Condition always true [test/compress/issue-1261.js:143,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:144,23]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:145,24]", - "WARN: Condition always true [test/compress/issue-1261.js:145,8]", - "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:146,31]", - "WARN: Condition always false [test/compress/issue-1261.js:146,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:144,31]", + "WARN: Condition always false [test/compress/issue-1261.js:144,8]", ] } diff --git a/test/compress/issue-1275.js b/test/compress/issue-1275.js index 5d4f5b70..2553c74f 100644 --- a/test/compress/issue-1275.js +++ b/test/compress/issue-1275.js @@ -9,7 +9,6 @@ string_plus_optimization: { unused : true, if_return : true, join_vars : true, - cascade : true, hoist_funs : true, }; input: { diff --git a/test/compress/issue-1447.js b/test/compress/issue-1447.js index a7f35e5a..0a765685 100644 --- a/test/compress/issue-1447.js +++ b/test/compress/issue-1447.js @@ -32,7 +32,6 @@ conditional_false_stray_else_in_loop: { hoist_vars : true, join_vars : true, if_return : true, - cascade : true, conditionals : false, } input: { diff --git a/test/compress/issue-1639.js b/test/compress/issue-1639.js index fc3db983..80d45a76 100644 --- a/test/compress/issue-1639.js +++ b/test/compress/issue-1639.js @@ -2,7 +2,7 @@ issue_1639_1: { options = { booleans: true, - cascade: true, + collapse_vars: true, conditionals: true, evaluate: true, join_vars: true, @@ -35,7 +35,7 @@ issue_1639_1: { issue_1639_2: { options = { booleans: true, - cascade: true, + collapse_vars: true, conditionals: true, evaluate: true, join_vars: true, @@ -68,7 +68,7 @@ issue_1639_2: { issue_1639_3: { options = { booleans: true, - cascade: true, + collapse_vars: true, conditionals: true, evaluate: true, sequences: true, diff --git a/test/compress/issue-1656.js b/test/compress/issue-1656.js index 27d87652..e44e2094 100644 --- a/test/compress/issue-1656.js +++ b/test/compress/issue-1656.js @@ -1,7 +1,6 @@ f7: { options = { booleans: true, - cascade: true, collapse_vars: true, comparisons: true, conditionals: true, diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js index 6a93136f..1e532dfb 100644 --- a/test/compress/issue-281.js +++ b/test/compress/issue-281.js @@ -453,7 +453,7 @@ pure_annotation_2: { drop_fargs: { options = { - cascade: true, + collapse_vars: true, inline: true, keep_fargs: false, side_effects: true, @@ -476,7 +476,7 @@ drop_fargs: { keep_fargs: { options = { - cascade: true, + collapse_vars: true, inline: true, keep_fargs: true, side_effects: true, diff --git a/test/compress/issue-368.js b/test/compress/issue-368.js index 5960aa64..b0491c29 100644 --- a/test/compress/issue-368.js +++ b/test/compress/issue-368.js @@ -1,6 +1,6 @@ collapse: { options = { - cascade: true, + collapse_vars: true, sequences: true, side_effects: true, unused: true, @@ -41,7 +41,7 @@ collapse: { return void 0 !== ('function' === typeof b ? b() : b) && c(); } function f2(b) { - return b = c(), 'stirng' == typeof ('function' === typeof b ? b() : b) && d(); + return 'stirng' == typeof ('function' === typeof (b = c()) ? b() : b) && d(); } function f3(c) { var a; diff --git a/test/compress/issue-892.js b/test/compress/issue-892.js index b6938c42..81df1cab 100644 --- a/test/compress/issue-892.js +++ b/test/compress/issue-892.js @@ -18,7 +18,6 @@ dont_mangle_arguments: { hoist_vars : true, if_return : true, join_vars : true, - cascade : true, side_effects : true, negate_iife : false }; diff --git a/test/compress/issue-976.js b/test/compress/issue-976.js index b711051b..54d7dad5 100644 --- a/test/compress/issue-976.js +++ b/test/compress/issue-976.js @@ -2,7 +2,7 @@ eval_collapse_vars: { options = { collapse_vars:true, sequences:false, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true }; input: { function f1() { diff --git a/test/compress/issue-979.js b/test/compress/issue-979.js index 7ed5801d..b2500126 100644 --- a/test/compress/issue-979.js +++ b/test/compress/issue-979.js @@ -2,7 +2,7 @@ issue979_reported: { options = { sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f1() { @@ -32,7 +32,7 @@ issue979_test_negated_is_best: { options = { sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, side_effects:true } input: { function f3() { diff --git a/test/compress/properties.js b/test/compress/properties.js index 61d15e23..0dcc28e8 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1331,3 +1331,43 @@ issue_2513: { "undefined undefined", ] } + +const_prop_assign_strict: { + options = { + pure_getters: "strict", + side_effects: true, + } + input: { + function Simulator() { + /abc/.index = 1; + this._aircraft = []; + } + (function() {}).prototype.destroy = x(); + } + expect: { + function Simulator() { + this._aircraft = []; + } + x(); + } +} + +const_prop_assign_pure: { + options = { + pure_getters: true, + side_effects: true, + } + input: { + function Simulator() { + /abc/.index = 1; + this._aircraft = []; + } + (function() {}).prototype.destroy = x(); + } + expect: { + function Simulator() { + this._aircraft = []; + } + x(); + } +} diff --git a/test/compress/pure_funcs.js b/test/compress/pure_funcs.js index 3cc529a8..d15bcca3 100644 --- a/test/compress/pure_funcs.js +++ b/test/compress/pure_funcs.js @@ -293,3 +293,124 @@ unary: { bar(); } } + +issue_2629_1: { + options = { + side_effects: true, + } + beautify = { + comments: "all", + } + input: { + /*@__PURE__*/ a(); + /*@__PURE__*/ (b()); + (/*@__PURE__*/ c)(); + (/*@__PURE__*/ d()); + } + expect_exact: [ + "/* */c();", + ] +} + +issue_2629_2: { + options = { + side_effects: true, + } + beautify = { + comments: "all", + } + input: { + /*@__PURE__*/ a(1)(2)(3); + /*@__PURE__*/ (b(1))(2)(3); + /*@__PURE__*/ (c(1)(2))(3); + /*@__PURE__*/ (d(1)(2)(3)); + (/*@__PURE__*/ e)(1)(2)(3); + (/*@__PURE__*/ f(1))(2)(3); + (/*@__PURE__*/ g(1)(2))(3); + (/*@__PURE__*/ h(1)(2)(3)); + } + expect_exact: [ + "/* */e(1)(2)(3);", + "/* */f(1)(2)(3);", + "/* */g(1)(2)(3);", + ] +} + +issue_2629_3: { + options = { + side_effects: true, + } + beautify = { + comments: "all", + } + input: { + /*@__PURE__*/ a.x(1).y(2).z(3); + /*@__PURE__*/ (b.x)(1).y(2).z(3); + /*@__PURE__*/ (c.x(1)).y(2).z(3); + /*@__PURE__*/ (d.x(1).y)(2).z(3); + /*@__PURE__*/ (e.x(1).y(2)).z(3); + /*@__PURE__*/ (f.x(1).y(2).z)(3); + /*@__PURE__*/ (g.x(1).y(2).z(3)); + (/*@__PURE__*/ h).x(1).y(2).z(3); + (/*@__PURE__*/ i.x)(1).y(2).z(3); + (/*@__PURE__*/ j.x(1)).y(2).z(3); + (/*@__PURE__*/ k.x(1).y)(2).z(3); + (/*@__PURE__*/ l.x(1).y(2)).z(3); + (/*@__PURE__*/ m.x(1).y(2).z)(3); + (/*@__PURE__*/ n.x(1).y(2).z(3)); + } + expect_exact: [ + "/* */h.x(1).y(2).z(3);", + "/* */i.x(1).y(2).z(3);", + "/* */j.x(1).y(2).z(3);", + "/* */k.x(1).y(2).z(3);", + "/* */l.x(1).y(2).z(3);", + "/* */m.x(1).y(2).z(3);", + ] +} + +issue_2629_4: { + options = { + side_effects: true, + } + input: { + (/*@__PURE__*/ x(), y()); + (w(), /*@__PURE__*/ x(), y()); + } + expect: { + y(); + w(), y(); + } +} + +issue_2629_5: { + options = { + side_effects: true, + } + input: { + [ /*@__PURE__*/ x() ]; + [ /*@__PURE__*/ x(), y() ]; + [ w(), /*@__PURE__*/ x(), y() ]; + } + expect: { + y(); + w(), y(); + } +} + +issue_2638: { + options = { + side_effects: true, + } + beautify = { + comments: "all", + } + input: { + /*@__PURE__*/(g() || h())(x(), y()); + (/*@__PURE__*/ (a() || b()))(c(), d()); + } + expect_exact: [ + "/* */x(),y();", + "/* */(a()||b())(c(),d());", + ] +} diff --git a/test/compress/pure_getters.js b/test/compress/pure_getters.js index 33b7fea6..9379c609 100644 --- a/test/compress/pure_getters.js +++ b/test/compress/pure_getters.js @@ -185,7 +185,7 @@ impure_getter_2: { issue_2110_1: { options = { - cascade: true, + collapse_vars: true, pure_getters: "strict", sequences: true, side_effects: true, @@ -274,7 +274,7 @@ set_immutable_1: { set_immutable_2: { options = { - cascade: true, + collapse_vars: true, conditionals: true, pure_getters: "strict", reduce_funcs: true, @@ -324,7 +324,7 @@ set_immutable_3: { set_immutable_4: { options = { - cascade: true, + collapse_vars: true, conditionals: true, pure_getters: "strict", reduce_funcs: true, @@ -375,7 +375,7 @@ set_mutable_1: { set_mutable_2: { options = { - cascade: true, + collapse_vars: true, conditionals: true, pure_getters: "strict", reduce_funcs: true, @@ -476,7 +476,7 @@ issue_2265_4: { issue_2313_1: { options = { - cascade: true, + collapse_vars: true, conditionals: true, pure_getters: "strict", sequences: true, @@ -522,7 +522,7 @@ issue_2313_1: { issue_2313_2: { options = { - cascade: true, + collapse_vars: true, conditionals: true, pure_getters: true, sequences: true, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 080307f5..f04f7c75 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1396,7 +1396,7 @@ defun_inline_3: { options = { evaluate: true, inline: true, - passes: 2, + passes: 3, reduce_funcs: true, reduce_vars: true, side_effects: true, @@ -2250,23 +2250,22 @@ redefine_farg_2: { console.log(f([]), g([]), h([])); } expect: { - console.log(function(a) { - return typeof a; - }([]), "number",function(a, b) { + console.log((a = [], typeof a), "number",function(a, b) { a = b; return typeof a; }([])); + var a; } expect_stdout: "object number undefined" } redefine_farg_3: { options = { - cascade: true, + collapse_vars: true, evaluate: true, inline: true, keep_fargs: false, - passes: 2, + passes: 3, reduce_funcs: true, reduce_vars: true, sequences: true, @@ -3217,6 +3216,7 @@ obj_var_2: { obj_arg_1: { options = { + collapse_vars: true, evaluate: true, inline: true, passes: 2, @@ -3248,9 +3248,10 @@ obj_arg_1: { obj_arg_2: { options = { + collapse_vars: true, evaluate: true, inline: true, - passes: 2, + passes: 3, properties: true, reduce_funcs: true, reduce_vars: true, @@ -3671,6 +3672,37 @@ escaped_prop_1: { } escaped_prop_2: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + passes: 2, + pure_getters: "strict", + reduce_funcs: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var obj = { o: { a: 1 } }; + (function(o) { + o.a++; + })(obj.o); + (function(o) { + console.log(o.a); + })(obj.o); + } + expect: { + var obj = { o: { a: 1 } }; + obj.o.a++; + console.log(obj.o.a); + } + expect_stdout: "2" +} + +escaped_prop_3: { options = { reduce_funcs: true, reduce_vars: true, @@ -4907,7 +4939,7 @@ issue_2455: { } } -issue_2560_1: { +escape_conditional: { options = { reduce_funcs: true, reduce_vars: true, @@ -4946,7 +4978,7 @@ issue_2560_1: { expect_stdout: "PASS" } -issue_2560_2: { +escape_sequence: { options = { reduce_funcs: true, reduce_vars: true, @@ -4984,7 +5016,7 @@ issue_2560_2: { expect_stdout: "PASS" } -issue_2560_3: { +escape_throw: { options = { reduce_funcs: true, reduce_vars: true, @@ -5029,7 +5061,7 @@ issue_2560_3: { expect_stdout: "PASS" } -issue_2560_4: { +escape_local_conditional: { options = { reduce_funcs: true, reduce_vars: true, @@ -5066,7 +5098,7 @@ issue_2560_4: { expect_stdout: "PASS" } -issue_2560_5: { +escape_local_sequence: { options = { reduce_funcs: true, reduce_vars: true, @@ -5103,7 +5135,7 @@ issue_2560_5: { expect_stdout: "PASS" } -issue_2560_6: { +escape_local_throw: { options = { reduce_funcs: true, reduce_vars: true, @@ -5276,3 +5308,141 @@ escape_expansion: { expect_stdout: "PASS" node_version: ">=6" } + +inverted_var: { + options = { + evaluate: true, + inline: true, + passes: 3, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + console.log(function() { + var a = 1; + return a; + }(), function() { + var b; + b = 2; + return b; + }(), function() { + c = 3; + return c; + var c; + }(), function(c) { + c = 4; + return c; + }(), function (c) { + c = 5; + return c; + var c; + }(), function c() { + c = 6; + return c; + }(), function c() { + c = 7; + return c; + var c; + }(), function() { + c = 8; + return c; + var c = "foo"; + }()); + } + expect: { + console.log(1, 2, 3, 4, 5, function c() { + c = 6; + return c; + }(), 7, function() { + c = 8; + return c; + var c = "foo"; + }()); + } + expect_stdout: true +} + +defun_single_use_loop: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + for (var x, i = 2; --i >= 0; ) { + var y = x; + x = f; + console.log(x === y); + } + function f() {}; + } + expect: { + for (var x, i = 2; --i >= 0; ) { + var y = x; + x = f; + console.log(x === y); + } + function f() {}; + } + expect_stdout: [ + "false", + "true", + ] +} + +do_while: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f(a) { + do { + (function() { + a && (c = "PASS"); + })(); + } while (a = 0); + } + var c = "FAIL"; + f(1); + console.log(c); + } + expect: { + function f(a) { + do { + (function() { + a && (c = "PASS"); + })(); + } while (a = 0); + } + var c = "FAIL"; + f(1); + console.log(c); + } + expect_stdout: "PASS" +} + +issue_2598: { + options = { + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function f() {} + function g(a) { + return a || f; + } + console.log(g(false) === g(null)); + } + expect: { + function f() {} + function g(a) { + return a || f; + } + console.log(g(false) === g(null)); + } + expect_stdout: "true" +} diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js index 4d2b4257..c7e09067 100644 --- a/test/compress/return_undefined.js +++ b/test/compress/return_undefined.js @@ -17,7 +17,6 @@ return_undefined: { keep_fnames : false, hoist_vars : true, join_vars : true, - cascade : true, negate_iife : true }; input: { diff --git a/test/compress/sequences.js b/test/compress/sequences.js index b7a72434..53ce1850 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -317,7 +317,7 @@ unsafe_undefined: { issue_1685: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -341,7 +341,7 @@ issue_1685: { func_def_1: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -361,7 +361,7 @@ func_def_1: { func_def_2: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -379,7 +379,7 @@ func_def_2: { func_def_3: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -401,7 +401,7 @@ func_def_3: { func_def_4: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -427,7 +427,7 @@ func_def_4: { func_def_5: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -634,7 +634,7 @@ side_effects: { side_effects_cascade_1: { options = { - cascade: true, + collapse_vars: true, conditionals: true, sequences: true, side_effects: true, @@ -655,7 +655,7 @@ side_effects_cascade_1: { side_effects_cascade_2: { options = { - cascade: true, + collapse_vars: true, side_effects: true, } input: { @@ -677,7 +677,7 @@ side_effects_cascade_2: { side_effects_cascade_3: { options = { - cascade: true, + collapse_vars: true, conditionals: true, side_effects: true, } @@ -692,14 +692,14 @@ side_effects_cascade_3: { expect: { function f(a, b) { !(b += a) && ((b = a) || (b -= a, b ^= a)), - --a; + a--; } } } issue_27: { options = { - cascade: true, + collapse_vars: true, passes: 2, sequences: true, side_effects: true, @@ -746,7 +746,7 @@ reassign_const: { issue_2062: { options = { booleans: true, - cascade: true, + collapse_vars: true, conditionals: true, side_effects: true, } @@ -765,7 +765,7 @@ issue_2062: { issue_2313: { options = { - cascade: true, + collapse_vars: true, sequences: true, side_effects: true, } @@ -803,3 +803,20 @@ issue_2313: { } expect_stdout: "2 1" } + +cascade_assignment_in_return: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function f(a, b) { + return a = x(), b(a); + } + } + expect: { + function f(a, b) { + return b(x()); + } + } +} diff --git a/test/compress/unicode.js b/test/compress/unicode.js index d57165ea..586b6f7b 100644 --- a/test/compress/unicode.js +++ b/test/compress/unicode.js @@ -119,3 +119,10 @@ issue_2242_4: { } expect_exact: 'console.log("\ud83d\ude00","\\ud83d@\\ude00");' } + +issue_2569: { + input: { + new RegExp("[\udc42-\udcaa\udd74-\udd96\ude45-\ude4f\udea3-\udecc]"); + } + expect_exact: 'new RegExp("[\\udc42-\\udcaa\\udd74-\\udd96\\ude45-\\ude4f\\udea3-\\udecc]");' +} diff --git a/test/mocha/comment-filter.js b/test/mocha/comment-filter.js index 0e4f3dff..25233d11 100644 --- a/test/mocha/comment-filter.js +++ b/test/mocha/comment-filter.js @@ -14,7 +14,7 @@ describe("comment filters", function() { it("Should be able to filter commments with the 'some' option", function() { var ast = UglifyJS.parse("// foo\n/*@preserve*/\n// bar\n/*@license*/\n//@license with the wrong comment type\n/*@cc_on something*/"); - assert.strictEqual(ast.print_to_string({comments: "some"}), "/*@preserve*/\n/*@license*/\n/*@cc_on something*/\n"); + assert.strictEqual(ast.print_to_string({comments: "some"}), "/*@preserve*/\n/*@license*/\n/*@cc_on something*/"); }); it("Should be able to filter comments by passing a function", function() { @@ -55,12 +55,12 @@ describe("comment filters", function() { return true; }; - assert.strictEqual(ast.print_to_string({comments: f}), "#!Random comment\n//test1\n/*test2*/\n"); + assert.strictEqual(ast.print_to_string({comments: f}), "#!Random comment\n//test1\n/*test2*/"); }); it("Should never be able to filter comment5 when using 'some' as filter", function() { var ast = UglifyJS.parse("#!foo\n//foo\n/*@preserve*/\n/* please hide me */"); - assert.strictEqual(ast.print_to_string({comments: "some"}), "#!foo\n/*@preserve*/\n"); + assert.strictEqual(ast.print_to_string({comments: "some"}), "#!foo\n/*@preserve*/"); }); it("Should have no problem on multiple calls", function() { diff --git a/test/mocha/comment.js b/test/mocha/comment.js index 42b9e5a2..5968b569 100644 --- a/test/mocha/comment.js +++ b/test/mocha/comment.js @@ -48,4 +48,176 @@ describe("Comment", function() { }, fail, tests[i]); } }); + + it("Should handle comment within return correctly", function() { + var result = uglify.minify([ + "function unequal(x, y) {", + " return (", + " // Either one", + " x < y", + " ||", + " y < x", + " );", + "}", + ].join("\n"), { + compress: false, + mangle: false, + output: { + beautify: true, + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, [ + "function unequal(x, y) {", + " // Either one", + " return x < y || y < x;", + "}", + ].join("\n")); + }); + + it("Should handle comment folded into return correctly", function() { + var result = uglify.minify([ + "function f() {", + " /* boo */ x();", + " return y();", + "}", + ].join("\n"), { + mangle: false, + output: { + beautify: true, + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, [ + "function f() {", + " /* boo */", + " return x(), y();", + "}", + ].join("\n")); + }); + + it("Should not drop comments after first OutputStream", function() { + var code = "/* boo */\nx();"; + var ast = uglify.parse(code); + var out1 = uglify.OutputStream({ + beautify: true, + comments: "all", + }); + ast.print(out1); + var out2 = uglify.OutputStream({ + beautify: true, + comments: "all", + }); + ast.print(out2); + assert.strictEqual(out1.get(), code); + assert.strictEqual(out2.get(), out1.get()); + }); + + it("Should retain trailing comments", function() { + var code = [ + "if (foo /* lost comment */ && bar /* lost comment */) {", + " // this one is kept", + " {/* lost comment */}", + " !function() {", + " // lost comment", + " }();", + " function baz() {/* lost comment */}", + " // lost comment", + "}", + "// comments right before EOF are lost as well", + ].join("\n"); + var result = uglify.minify(code, { + compress: false, + mangle: false, + output: { + beautify: true, + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, code); + }); + + it("Should correctly preserve new lines around comments", function() { + var tests = [ + [ + "// foo", + "// bar", + "x();", + ].join("\n"), + [ + "// foo", + "/* bar */", + "x();", + ].join("\n"), + [ + "// foo", + "/* bar */ x();", + ].join("\n"), + [ + "/* foo */", + "// bar", + "x();", + ].join("\n"), + [ + "/* foo */ // bar", + "x();", + ].join("\n"), + [ + "/* foo */", + "/* bar */", + "x();", + ].join("\n"), + [ + "/* foo */", + "/* bar */ x();", + ].join("\n"), + [ + "/* foo */ /* bar */", + "x();", + ].join("\n"), + "/* foo */ /* bar */ x();", + ].forEach(function(code) { + var result = uglify.minify(code, { + compress: false, + mangle: false, + output: { + beautify: true, + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, code); + }); + }); + + it("Should preserve new line before comment without beautify", function() { + var code = [ + "function f(){", + "/* foo */bar()}", + ].join("\n"); + var result = uglify.minify(code, { + compress: false, + mangle: false, + output: { + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, code); + }); + + it("Should preserve comments around IIFE", function() { + var result = uglify.minify("/*a*/(/*b*/function(){/*c*/}/*d*/)/*e*/();", { + compress: false, + mangle: false, + output: { + comments: "all", + }, + }); + if (result.error) throw result.error; + assert.strictEqual(result.code, "/*a*/ /*b*/(function(){/*c*/}/*d*/ /*e*/)();"); + }); }); diff --git a/test/mocha/glob.js b/test/mocha/glob.js index eb5d477d..58c40cf0 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -11,7 +11,7 @@ describe("bin/uglifyjs with input file globs", function() { exec(command, function(err, stdout) { if (err) throw err; - assert.strictEqual(stdout, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);\n'); + assert.strictEqual(stdout, 'var print=console.log.bind(console);function foo(o){print("Foo:",2*o)}\n'); done(); }); }); @@ -26,7 +26,7 @@ describe("bin/uglifyjs with input file globs", function() { }); }); it("bin/uglifyjs with multiple input file globs.", function(done) { - var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel,passes=2'; + var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel,passes=3'; exec(command, function(err, stdout) { if (err) throw err; diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 05e31944..6bae59c9 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -43,7 +43,7 @@ describe("minify", function() { compressed += result.code; }); assert.strictEqual(JSON.stringify(cache).slice(0, 20), '{"cname":5,"props":{'); - assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);'); + assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}var c=console.log.bind(console);function l(o){c("Foo:",2*o)}var f=n(3),i=r(12);c("qux",f,i),l(11);'); assert.strictEqual(run_code(compressed), run_code(original)); }); @@ -69,7 +69,7 @@ describe("minify", function() { compressed += result.code; }); assert.strictEqual(JSON.stringify(cache).slice(0, 28), '{"vars":{"cname":5,"props":{'); - assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);'); + assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}var c=console.log.bind(console);function l(o){c("Foo:",2*o)}var f=n(3),i=r(12);c("qux",f,i),l(11);'); assert.strictEqual(run_code(compressed), run_code(original)); }); @@ -248,7 +248,7 @@ describe("minify", function() { var code = result.code; assert.strictEqual(code, "// comment1 comment2\nbar();"); }); - it("should not drop #__PURE__ hint if function is retained", function() { + it("should drop #__PURE__ hint if function is retained", function() { var result = Uglify.minify("var a = /*#__PURE__*/(function(){ foo(); })();", { output: { comments: "all", @@ -256,7 +256,7 @@ describe("minify", function() { } }); var code = result.code; - assert.strictEqual(code, "var a=/*#__PURE__*/function(){foo()}();"); + assert.strictEqual(code, "var a=/* */function(){foo()}();"); }) }); diff --git a/test/sandbox.js b/test/sandbox.js index fe2e588e..2ce9f6e1 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -37,6 +37,7 @@ var FUNC_TOSTRING = [ ' return "[Function: " + i + "]";', " }", "}();", + 'Object.defineProperty(Function.prototype, "valueOf", { enumerable: false });', ]).join("\n"); exports.run_code = function(code) { var stdout = ""; diff --git a/test/ufuzz.js b/test/ufuzz.js index 578103e8..14b8f114 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -263,10 +263,8 @@ var CAN_CONTINUE = true; var CANNOT_CONTINUE = false; var CAN_RETURN = false; var CANNOT_RETURN = true; -var NOT_GLOBAL = true; -var IN_GLOBAL = true; -var ANY_TYPE = false; -var NO_DECL = true; +var NO_DEFUN = false; +var DEFUN_OK = true; var DONT_STORE = true; var VAR_NAMES = [ @@ -307,6 +305,7 @@ var TYPEOF_OUTCOMES = [ var unique_vars = []; var loops = 0; var funcs = 0; +var called = Object.create(null); var labels = 10000; function rng(max) { @@ -323,21 +322,22 @@ function createTopLevelCode() { unique_vars.length = 0; loops = 0; funcs = 0; + called = Object.create(null); return [ strictMode(), - 'var a = 100, b = 10, c = 0;', + 'var _calls_ = 10, a = 100, b = 10, c = 0;', rng(2) == 0 ? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0) - : createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, IN_GLOBAL, ANY_TYPE, CANNOT_THROW, 0), + : createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, DEFUN_OK, CANNOT_THROW, 0), 'console.log(null, a, b, c);' // preceding `null` makes for a cleaner output (empty string still shows up etc) ].join('\n'); } -function createFunctions(n, recurmax, inGlobal, noDecl, canThrow, stmtDepth) { +function createFunctions(n, recurmax, allowDefun, canThrow, stmtDepth) { if (--recurmax < 0) { return ';'; } var s = ''; while (n-- > 0) { - s += createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) + '\n'; + s += createFunction(recurmax, allowDefun, canThrow, stmtDepth) + '\n'; } return s; } @@ -363,16 +363,16 @@ function filterDirective(s) { return s; } -function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { +function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { if (--recurmax < 0) { return ';'; } if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; - var func = funcs++; var namesLenBefore = VAR_NAMES.length; var name; - if (inGlobal || rng(5) > 0) name = 'f' + func; - else { + if (allowDefun || rng(5) > 0) { + name = 'f' + funcs++; + } else { unique_vars.push('a', 'b', 'c'); - name = createVarName(MANDATORY, noDecl); + name = createVarName(MANDATORY, !allowDefun); unique_vars.length -= 3; } var s = [ @@ -381,7 +381,7 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { ]; if (rng(5) === 0) { // functions with functions. lower the recursion to prevent a mess. - s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), NOT_GLOBAL, ANY_TYPE, canThrow, stmtDepth)); + s.push(createFunctions(rng(5) + 1, Math.ceil(recurmax * 0.7), DEFUN_OK, canThrow, stmtDepth)); } else { // functions with statements s.push(createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); @@ -391,12 +391,16 @@ function createFunction(recurmax, inGlobal, noDecl, canThrow, stmtDepth) { VAR_NAMES.length = namesLenBefore; - if (noDecl) s = 'var ' + createVarName(MANDATORY) + ' = ' + s; - // avoid "function statements" (decl inside statements) - else if (inGlobal || rng(10) > 0) s += 'var ' + createVarName(MANDATORY) + ' = ' + name; - s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ');'; + if (!allowDefun) { + // avoid "function statements" (decl inside statements) + s = 'var ' + createVarName(MANDATORY) + ' = ' + s; + s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ')'; + } else if (!(name in called) || rng(3) > 0) { + s += 'var ' + createVarName(MANDATORY) + ' = ' + name; + s += '(' + createArgs(recurmax, stmtDepth, canThrow) + ')'; + } - return s; + return s + ';'; } function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { @@ -541,7 +545,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn case STMT_FUNC_EXPR: // "In non-strict mode code, functions can only be declared at top level, inside a block, or ..." // (dont both with func decls in `if`; it's only a parser thing because you cant call them without a block) - return '{' + createFunction(recurmax, NOT_GLOBAL, NO_DECL, canThrow, stmtDepth) + '}'; + return '{' + createFunction(recurmax, NO_DEFUN, canThrow, stmtDepth) + '}'; case STMT_TRY: // catch var could cause some problems // note: the "blocks" are syntactically mandatory for try/catch/finally @@ -648,7 +652,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { '(function ' + name + '(){', strictMode(), createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - '})()' + rng(2) == 0 ? '})' : '})()' ); break; case 1: @@ -687,7 +691,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { } s.push( createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - '}' + rng(2) == 0 ? '}' : '}()' ); break; } @@ -754,6 +758,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: var name = getVarName(); return name + ' && ' + name + '.' + getDotKey(); + case p++: + case p++: + case p++: + case p++: + var name = rng(3) == 0 ? getVarName() : 'f' + rng(funcs + 2); + called[name] = true; + return 'typeof ' + name + ' == "function" && --_calls_ >= 0 && ' + name + '(' + createArgs(recurmax, stmtDepth, canThrow) + ')'; } _createExpression.N = p; return _createExpression(recurmax, noComma, stmtDepth, canThrow); diff --git a/test/ufuzz.json b/test/ufuzz.json index 0d737d31..4057a351 100644 --- a/test/ufuzz.json +++ b/test/ufuzz.json @@ -15,15 +15,12 @@ }, {}, { - "compress": { - "hoist_props": true - }, "toplevel": true }, { "compress": { "keep_fargs": false, - "passes": 3 + "passes": 100 } } ] diff --git a/tools/exports.js b/tools/exports.js index e08296f3..c09436ab 100644 --- a/tools/exports.js +++ b/tools/exports.js @@ -2,4 +2,5 @@ exports["Dictionary"] = Dictionary; exports["TreeWalker"] = TreeWalker; exports["TreeTransformer"] = TreeTransformer; exports["minify"] = minify; +exports["parse"] = parse; exports["_push_uniq"] = push_uniq;