diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2017-02-24 07:33:57 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-24 07:33:57 +0800 |
commit | 229e42cdee14c384b7a48bd0882bb27d98db040a (patch) | |
tree | aefc78b2d7dded10d049296336324933596d770d | |
parent | eb55d8a9bb37cc28303ace91337784dbf0777d03 (diff) | |
parent | 4e49302916fe395f5c63992aa28c33392208fb27 (diff) | |
download | tracifyjs-229e42cdee14c384b7a48bd0882bb27d98db040a.tar.gz tracifyjs-229e42cdee14c384b7a48bd0882bb27d98db040a.zip |
Merge pull request #1485 from alexlamsl/merge-2.8.0
2.8.0 staging
48 files changed, 4187 insertions, 552 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6edcf864 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.js text eol=lf @@ -346,6 +346,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). comparison are switching. Compression only works if both `comparisons` and `unsafe_comps` are both set to true. +- `unsafe_proto` (default: false) -- optimize expressions like + `Array.prototype.slice.call(a)` into `[].slice.call(a)` + - `conditionals` -- apply optimizations for `if`-s and conditional expressions @@ -361,7 +364,15 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `loops` -- optimizations for `do`, `while` and `for` loops when we can statically determine the condition -- `unused` -- drop unreferenced functions and variables +- `unused` -- drop unreferenced functions and variables (simple direct variable + assignments do not count as references unless set to `"keep_assign"`) + +- `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) + in the toplevel scope (`false` by default, `true` to drop both unreferenced + functions and variables) + +- `top_retain` -- prevent specific toplevel functions and variables from `unused` + removal (can be array, comma-separated, RegExp or function. Implies `toplevel`) - `hoist_funs` -- hoist function declarations @@ -446,6 +457,8 @@ if (DEBUG) { } ``` +You can specify nested constants in the form of `--define env.DEBUG=false`. + UglifyJS will warn about the condition being always false and about dropping unreachable code; for now there is no option to turn off only this specific warning, you can pass `warnings=false` to turn off *all* warnings. @@ -456,8 +469,6 @@ separate file and include it into the build. For example you can have a ```javascript const DEBUG = false; const PRODUCTION = true; -// Alternative for environments that don't support `const` -/** @const */ var STAGING = false; // etc. ``` @@ -468,7 +479,8 @@ and build your code like this: UglifyJS will notice the constants and, since they cannot be altered, it will evaluate references to them to the value itself and drop unreachable code as usual. The build will contain the `const` declarations if you use -them. If you are targeting < ES6 environments, use `/** @const */ var`. +them. If you are targeting < ES6 environments which does not support `const`, +using `var` with `reduce_vars` (enabled by default) should suffice. <a name="codegen-options"></a> diff --git a/bin/uglifyjs b/bin/uglifyjs index 8cb2f0df..27717fb6 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -228,9 +228,10 @@ if (ARGS.mangle_props === true) { } var OUTPUT_OPTIONS = { - beautify : BEAUTIFY ? true : false, - preamble : ARGS.preamble || null, - quote_style : ARGS.quotes != null ? ARGS.quotes : 0 + beautify : BEAUTIFY ? true : false, + max_line_len : 32000, + preamble : ARGS.preamble || null, + quote_style : ARGS.quotes != null ? ARGS.quotes : 0, }; if (ARGS.mangle_props == 2) { @@ -540,7 +541,7 @@ function getOptions(flag, constants) { ast.walk(new UglifyJS.TreeWalker(function(node){ if (node instanceof UglifyJS.AST_Seq) return; // descend if (node instanceof UglifyJS.AST_Assign) { - var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_"); + var name = node.left.print_to_string().replace(/-/g, "_"); var value = node.right; if (constants) value = new Function("return (" + value.print_to_string() + ")")(); @@ -548,7 +549,7 @@ function getOptions(flag, constants) { return true; // no descend } if (node instanceof UglifyJS.AST_Symbol || node instanceof UglifyJS.AST_Binary) { - var name = node.print_to_string({ beautify: false }).replace(/-/g, "_"); + var name = node.print_to_string().replace(/-/g, "_"); ret[name] = true; return true; // no descend } diff --git a/lib/compress.js b/lib/compress.js index 4e45df92..2bc1c5a5 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -54,20 +54,23 @@ function Compressor(options, false_by_default) { drop_debugger : !false_by_default, unsafe : false, unsafe_comps : false, + unsafe_proto : false, conditionals : !false_by_default, comparisons : !false_by_default, evaluate : !false_by_default, booleans : !false_by_default, loops : !false_by_default, unused : !false_by_default, + toplevel : !!options["top_retain"], + top_retain : null, hoist_funs : !false_by_default, keep_fargs : true, keep_fnames : false, hoist_vars : false, if_return : !false_by_default, join_vars : !false_by_default, - collapse_vars : false, - reduce_vars : false, + collapse_vars : !false_by_default, + reduce_vars : !false_by_default, cascade : !false_by_default, side_effects : !false_by_default, pure_getters : false, @@ -80,6 +83,29 @@ function Compressor(options, false_by_default) { global_defs : {}, passes : 1, }, true); + var pure_funcs = this.options["pure_funcs"]; + if (typeof pure_funcs == "function") { + this.pure_funcs = pure_funcs; + } else { + this.pure_funcs = pure_funcs ? function(node) { + return pure_funcs.indexOf(node.expression.print_to_string()) < 0; + } : return_true; + } + var top_retain = this.options["top_retain"]; + if (top_retain instanceof RegExp) { + this.top_retain = function(def) { + return top_retain.test(def.name); + }; + } else if (typeof top_retain === "function") { + this.top_retain = top_retain; + } else if (top_retain) { + if (typeof top_retain === "string") { + top_retain = top_retain.split(/,/); + } + this.top_retain = function(def) { + return top_retain.indexOf(def.name) >= 0; + }; + } var sequences = this.options["sequences"]; this.sequences_limit = sequences == 1 ? 200 : sequences | 0; this.warnings_produced = {}; @@ -91,7 +117,8 @@ merge(Compressor.prototype, { compress: function(node) { var passes = +this.options.passes || 1; for (var pass = 0; pass < passes && pass < 3; ++pass) { - if (pass > 0) node.clear_opt_flags(); + if (pass > 0 || this.option("reduce_vars")) + node.reset_opt_flags(this); node = node.transform(this); } return node; @@ -150,19 +177,45 @@ merge(Compressor.prototype, { return this.print_to_string() == node.print_to_string(); }); - AST_Node.DEFMETHOD("clear_opt_flags", function(){ - this.walk(new TreeWalker(function(node){ + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor){ + var reduce_vars = compressor.option("reduce_vars"); + var tw = new TreeWalker(function(node){ + if (reduce_vars && node instanceof AST_Scope) { + node.variables.each(function(def) { + delete def.modified; + }); + } if (node instanceof AST_SymbolRef) { var d = node.definition(); - if (d && d.init) { + if (d.init) { delete d.init._evaluated; } + if (reduce_vars && (d.orig.length > 1 || isModified(node, 0))) { + d.modified = true; + } + } + if (reduce_vars && node instanceof AST_Call && node.expression instanceof AST_Function) { + node.expression.argnames.forEach(function(arg, i) { + arg.definition().init = node.args[i] || make_node(AST_Undefined, node); + }); } if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; } - })); + }); + this.walk(tw); + + function isModified(node, level) { + var parent = tw.parent(level); + if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") + || parent instanceof AST_Assign && parent.left === node + || parent instanceof AST_Call && parent.expression === node) { + return true; + } else if (parent instanceof AST_PropAccess && parent.expression === node) { + return isModified(parent, level + 1); + } + } }); function make_node(ctor, orig, props) { @@ -175,22 +228,11 @@ merge(Compressor.prototype, { }; function make_node_from_constant(compressor, val, orig) { - // XXX: WIP. - // if (val instanceof AST_Node) return val.transform(new TreeTransformer(null, function(node){ - // if (node instanceof AST_SymbolRef) { - // var scope = compressor.find_parent(AST_Scope); - // var def = scope.find_variable(node); - // node.thedef = def; - // return node; - // } - // })).transform(compressor); - - if (val instanceof AST_Node) return val.transform(compressor); switch (typeof val) { case "string": return make_node(AST_String, orig, { value: val - }).optimize(compressor); + }); case "number": if (isNaN(val)) { return make_node(AST_NaN, orig); @@ -203,17 +245,17 @@ merge(Compressor.prototype, { }); } - return make_node(AST_Number, orig, { value: val }).optimize(compressor); + return make_node(AST_Number, orig, { value: val }); case "boolean": - return make_node(val ? AST_True : AST_False, orig).optimize(compressor); + return make_node(val ? AST_True : AST_False, orig).transform(compressor); case "undefined": - return make_node(AST_Undefined, orig).optimize(compressor); + return make_node(AST_Undefined, orig).transform(compressor); default: if (val === null) { - return make_node(AST_Null, orig, { value: null }).optimize(compressor); + return make_node(AST_Null, orig, { value: null }); } if (val instanceof RegExp) { - return make_node(AST_RegExp, orig, { value: val }).optimize(compressor); + return make_node(AST_RegExp, orig, { value: val }); } throw new Error(string_template("Can't handle constant of type: {type}", { type: typeof val @@ -261,6 +303,22 @@ merge(Compressor.prototype, { return x; }; + var readOnlyPrefix = makePredicate("! ~ + - void typeof"); + function statement_to_expression(stat) { + if (stat.body instanceof AST_UnaryPrefix && readOnlyPrefix(stat.body.operator)) { + return stat.body.expression; + } else { + return stat.body; + } + } + + 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; + } + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { @@ -286,10 +344,6 @@ merge(Compressor.prototype, { } } while (CHANGED && max_iter-- > 0); - if (compressor.option("negate_iife")) { - negate_iifes(statements, compressor); - } - return statements; function collapse_single_use_vars(statements, compressor) { @@ -437,7 +491,7 @@ merge(Compressor.prototype, { var_defs_removed = true; } // Further optimize statement after substitution. - stat.clear_opt_flags(); + stat.reset_opt_flags(compressor); compressor.warn("Replacing " + (is_constant ? "constant" : "variable") + " " + var_name + " [{file}:{line},{col}]", node.start); @@ -546,7 +600,7 @@ merge(Compressor.prototype, { var self = compressor.self(); var multiple_if_returns = has_multiple_if_returns(statements); var in_lambda = self instanceof AST_Lambda; - var ret = []; + var ret = []; // Optimized statements, build from tail to front loop: for (var i = statements.length; --i >= 0;) { var stat = statements[i]; switch (true) { @@ -607,19 +661,21 @@ merge(Compressor.prototype, { ret = funs.concat([ stat.transform(compressor) ]); continue loop; } + //--- - // XXX: what was the intention of this case? + // if (a) return b; if (c) return d; e; ==> return a ? b : c ? d : void e; + // // if sequences is not enabled, this can lead to an endless loop (issue #866). // however, with sequences on this helps producing slightly better output for // the example code. if (compressor.option("sequences") + && i > 0 && statements[i - 1] instanceof AST_If && statements[i - 1].body instanceof AST_Return && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement - && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { + && !stat.alternative) { CHANGED = true; ret.push(make_node(AST_Return, ret[0], { value: make_node(AST_Undefined, ret[0]) }).transform(compressor)); - ret = as_statement_array(stat.alternative).concat(ret); ret.unshift(stat); continue loop; } @@ -730,8 +786,11 @@ merge(Compressor.prototype, { seq = []; }; statements.forEach(function(stat){ - if (stat instanceof AST_SimpleStatement && seqLength(seq) < compressor.sequences_limit) { - seq.push(stat.body); + if (stat instanceof AST_SimpleStatement) { + if (seqLength(seq) >= compressor.sequences_limit) { + push_seq(); + } + seq.push(seq.length > 0 ? statement_to_expression(stat) : stat.body); } else { push_seq(); ret.push(stat); @@ -780,7 +839,7 @@ merge(Compressor.prototype, { stat.init = cons_seq(stat.init); } else if (!stat.init) { - stat.init = prev.body; + stat.init = statement_to_expression(prev); ret.pop(); } } catch(ex) { @@ -837,50 +896,6 @@ merge(Compressor.prototype, { }, []); }; - function negate_iifes(statements, compressor) { - function is_iife_call(node) { - if (node instanceof AST_Call) { - return node.expression instanceof AST_Function || is_iife_call(node.expression); - } - return false; - } - - statements.forEach(function(stat){ - if (stat instanceof AST_SimpleStatement) { - stat.body = (function transform(thing) { - return thing.transform(new TreeTransformer(function(node){ - if (node instanceof AST_New) { - return node; - } - if (is_iife_call(node)) { - return make_node(AST_UnaryPrefix, node, { - operator: "!", - expression: node - }); - } - else if (node instanceof AST_Call) { - node.expression = transform(node.expression); - } - else if (node instanceof AST_Seq) { - node.car = transform(node.car); - } - else if (node instanceof AST_Conditional) { - var expr = transform(node.condition); - if (expr !== node.condition) { - // it has been negated, reverse - node.condition = expr; - var tmp = node.consequent; - node.consequent = node.alternative; - node.alternative = tmp; - } - } - return node; - })); - })(stat.body); - } - }); - }; - }; function extract_functions_from_statement_array(statements) { @@ -981,11 +996,81 @@ merge(Compressor.prototype, { || parent instanceof AST_Assign && parent.left === node; } + (function (def){ + AST_Node.DEFMETHOD("resolve_defines", function(compressor) { + if (!compressor.option("global_defs")) return; + var def = this._find_defs(compressor, ""); + if (def) { + var node, parent = this, level = 0; + do { + node = parent; + parent = compressor.parent(level++); + } while (parent instanceof AST_PropAccess && parent.expression === node); + if (isLHS(node, parent)) { + compressor.warn('global_defs ' + this.print_to_string() + ' redefined [{file}:{line},{col}]', this.start); + } else { + return def; + } + } + }); + function to_node(compressor, value, orig) { + if (value instanceof AST_Node) return make_node(value.CTOR, orig, value); + if (Array.isArray(value)) return make_node(AST_Array, orig, { + elements: value.map(function(value) { + return to_node(compressor, value, orig); + }) + }); + if (value && typeof value == "object") { + var props = []; + for (var key in value) { + props.push(make_node(AST_ObjectKeyVal, orig, { + key: key, + value: to_node(compressor, value[key], orig) + })); + } + return make_node(AST_Object, orig, { + properties: props + }); + } + return make_node_from_constant(compressor, value, orig); + } + def(AST_Node, noop); + def(AST_Dot, function(compressor, suffix){ + return this.expression._find_defs(compressor, suffix + "." + this.property); + }); + def(AST_SymbolRef, function(compressor, suffix){ + if (!this.global()) return; + var name; + var defines = compressor.option("global_defs"); + if (defines && HOP(defines, (name = this.name + suffix))) { + var node = to_node(compressor, defines[name], this); + var top = compressor.find_parent(AST_Toplevel); + node.walk(new TreeWalker(function(node) { + if (node instanceof AST_SymbolRef) { + node.scope = top; + node.thedef = top.def_global(node); + } + })); + return node; + } + }); + })(function(node, func){ + node.DEFMETHOD("_find_defs", func); + }); + function best_of(ast1, ast2) { return ast1.print_to_string().length > ast2.print_to_string().length ? ast2 : ast1; - }; + } + + function best_of_statement(ast1, ast2) { + return best_of(make_node(AST_SimpleStatement, ast1, { + body: ast1 + }), make_node(AST_SimpleStatement, ast2, { + body: ast2 + })).body; + } // methods to evaluate a constant expression (function (def){ @@ -1167,7 +1252,7 @@ merge(Compressor.prototype, { this._evaluating = true; try { var d = this.definition(); - if (d && (d.constant || compressor.option("reduce_vars") && !d.modified) && d.init) { + if (compressor.option("reduce_vars") && !d.modified && d.init) { if (compressor.option("unsafe")) { if (!HOP(d.init, '_evaluated')) { d.init._evaluated = ev(d.init, compressor); @@ -1205,7 +1290,17 @@ merge(Compressor.prototype, { operator: "!", expression: exp }); - }; + } + function best(orig, alt, first_in_statement) { + var negated = basic_negation(orig); + if (first_in_statement) { + var stat = make_node(AST_SimpleStatement, alt, { + body: alt + }); + return best_of(negated, stat) === stat ? alt : negated; + } + return best_of(negated, alt); + } def(AST_Node, function(){ return basic_negation(this); }); @@ -1225,13 +1320,13 @@ merge(Compressor.prototype, { self.cdr = self.cdr.negate(compressor); return self; }); - def(AST_Conditional, function(compressor){ + def(AST_Conditional, function(compressor, first_in_statement){ var self = this.clone(); self.consequent = self.consequent.negate(compressor); self.alternative = self.alternative.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); }); - def(AST_Binary, function(compressor){ + def(AST_Binary, function(compressor, first_in_statement){ var self = this.clone(), op = this.operator; if (compressor.option("unsafe_comps")) { switch (op) { @@ -1248,23 +1343,39 @@ merge(Compressor.prototype, { case "!==": self.operator = "==="; return self; case "&&": self.operator = "||"; - self.left = self.left.negate(compressor); + self.left = self.left.negate(compressor, first_in_statement); self.right = self.right.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); case "||": self.operator = "&&"; - self.left = self.left.negate(compressor); + self.left = self.left.negate(compressor, first_in_statement); self.right = self.right.negate(compressor); - return best_of(basic_negation(this), self); + return best(this, self, first_in_statement); } return basic_negation(this); }); })(function(node, func){ - node.DEFMETHOD("negate", function(compressor){ - return func.call(this, compressor); + node.DEFMETHOD("negate", function(compressor, first_in_statement){ + return func.call(this, compressor, first_in_statement); }); }); + 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, last_comment; + if (this.start + && (comments = this.start.comments_before) + && comments.length + && /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) { + compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); + last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' '); + pure = true; + } + return this.pure = pure; + }); + // determine if expression has side effects (function(def){ def(AST_Node, return_true); @@ -1274,10 +1385,12 @@ merge(Compressor.prototype, { def(AST_This, return_false); def(AST_Call, function(compressor){ - var pure = compressor.option("pure_funcs"); - if (!pure) return true; - if (typeof pure == "function") return pure(this); - return pure.indexOf(this.expression.print_to_string()) < 0; + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return true; + for (var i = this.args.length; --i >= 0;) { + if (this.args[i].has_side_effects(compressor)) + return true; + } + return false; }); def(AST_Block, function(compressor){ @@ -1407,13 +1520,27 @@ merge(Compressor.prototype, { AST_Scope.DEFMETHOD("drop_unused", function(compressor){ var self = this; if (compressor.has_directive("use asm")) return self; + var toplevel = compressor.option("toplevel"); if (compressor.option("unused") - && !(self instanceof AST_Toplevel) + && (!(self instanceof AST_Toplevel) || toplevel) && !self.uses_eval - && !self.uses_with - ) { + && !self.uses_with) { + var assign_as_unused = !/keep_assign/.test(compressor.option("unused")); + var drop_funcs = /funcs/.test(toplevel); + var drop_vars = /vars/.test(toplevel); + if (!(self instanceof AST_Toplevel) || toplevel == true) { + drop_funcs = drop_vars = true; + } var in_use = []; - var in_use_ids = {}; // avoid expensive linear scans of in_use + var in_use_ids = Object.create(null); // avoid expensive linear scans of in_use + if (self instanceof AST_Toplevel && compressor.top_retain) { + self.variables.each(function(def) { + if (compressor.top_retain(def) && !(def.id in in_use_ids)) { + in_use_ids[def.id] = true; + in_use.push(def); + } + }); + } var initializations = new Dictionary(); // pass 1: find out which symbols are directly used in // this scope (not in nested scopes). @@ -1421,11 +1548,25 @@ merge(Compressor.prototype, { var tw = new TreeWalker(function(node, descend){ if (node !== self) { if (node instanceof AST_Defun) { + if (!drop_funcs && scope === self) { + var node_def = node.name.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); + } + } initializations.add(node.name.name, node); return true; // don't go in nested scopes } if (node instanceof AST_Definitions && scope === self) { node.definitions.forEach(function(def){ + if (!drop_vars) { + var node_def = def.name.definition(); + if (!(node_def.id in in_use_ids)) { + in_use_ids[node_def.id] = true; + in_use.push(node_def); + } + } if (def.value) { initializations.add(def.name.name, def.value); if (def.value.has_side_effects(compressor)) { @@ -1435,6 +1576,14 @@ merge(Compressor.prototype, { }); return true; } + if (assign_as_unused + && node instanceof AST_Assign + && node.operator == "=" + && node.left instanceof AST_SymbolRef + && scope === self) { + node.right.walk(tw); + return true; + } if (node instanceof AST_SymbolRef) { var node_def = node.definition(); if (!(node_def.id in in_use_ids)) { @@ -1477,11 +1626,17 @@ merge(Compressor.prototype, { // pass 3: we should drop declarations not in_use var tt = new TreeTransformer( function before(node, descend, in_list) { + if (node instanceof AST_Function + && node.name + && !compressor.option("keep_fnames") + && !(node.name.definition().id in in_use_ids)) { + node.name = null; + } if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { if (!compressor.option("keep_fargs")) { for (var a = node.argnames, i = a.length; --i >= 0;) { var sym = a[i]; - if (sym.unreferenced()) { + if (!(sym.definition().id in in_use_ids)) { a.pop(); compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { name : sym.name, @@ -1494,7 +1649,7 @@ merge(Compressor.prototype, { } } } - if (node instanceof AST_Defun && node !== self) { + if (drop_funcs && node instanceof AST_Defun && node !== self) { if (!(node.name.definition().id in in_use_ids)) { compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { name : node.name.name, @@ -1506,7 +1661,7 @@ merge(Compressor.prototype, { } return node; } - if (node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { + if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { var def = node.definitions.filter(function(def){ if (def.name.definition().id in in_use_ids) return true; var w = { @@ -1569,6 +1724,15 @@ merge(Compressor.prototype, { } return node; } + if (drop_vars && assign_as_unused + && node instanceof AST_Assign + && node.operator == "=" + && node.left instanceof AST_SymbolRef) { + var def = node.left.definition(); + if (!(def.id in in_use_ids) && self.variables.get(def.name) === def) { + return node.right; + } + } if (node instanceof AST_For) { descend(node, this); @@ -1724,12 +1888,151 @@ merge(Compressor.prototype, { return self; }); + // drop_side_effect_free() + // remove side-effect-free parts which only affects return value + (function(def){ + function return_this() { + return this; + } + + function return_null() { + return null; + } + + // Drop side-effect-free elements from an array of expressions. + // Returns an array of expressions with side-effects or null + // if all elements were dropped. Note: original array may be + // returned if nothing changed. + function trim(nodes, compressor, first_in_statement) { + var ret = [], changed = false; + for (var i = 0, ii = nodes.length; i < ii; i++) { + var node = nodes[i].drop_side_effect_free(compressor, first_in_statement); + changed |= node !== nodes[i]; + if (node) { + ret.push(node); + first_in_statement = false; + } + } + return changed ? ret.length ? ret : null : nodes; + } + + def(AST_Node, return_this); + def(AST_Constant, return_null); + def(AST_This, return_null); + def(AST_Call, function(compressor, first_in_statement){ + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this; + var args = trim(this.args, compressor, first_in_statement); + return args && AST_Seq.from_array(args); + }); + def(AST_Function, return_null); + def(AST_Binary, function(compressor, first_in_statement){ + var right = this.right.drop_side_effect_free(compressor); + if (!right) return this.left.drop_side_effect_free(compressor, first_in_statement); + switch (this.operator) { + case "&&": + case "||": + var node = this.clone(); + node.right = right; + return node; + default: + var left = this.left.drop_side_effect_free(compressor, first_in_statement); + if (!left) return this.right.drop_side_effect_free(compressor, first_in_statement); + return make_node(AST_Seq, this, { + car: left, + cdr: right + }); + } + }); + def(AST_Assign, return_this); + def(AST_Conditional, function(compressor){ + var consequent = this.consequent.drop_side_effect_free(compressor); + var alternative = this.alternative.drop_side_effect_free(compressor); + if (consequent === this.consequent && alternative === this.alternative) return this; + if (!consequent) return alternative ? make_node(AST_Binary, this, { + operator: "||", + left: this.condition, + right: alternative + }) : this.condition.drop_side_effect_free(compressor); + if (!alternative) return make_node(AST_Binary, this, { + operator: "&&", + left: this.condition, + right: consequent + }); + var node = this.clone(); + node.consequent = consequent; + node.alternative = alternative; + return node; + }); + def(AST_Unary, function(compressor, first_in_statement){ + switch (this.operator) { + case "delete": + case "++": + case "--": + return this; + case "typeof": + if (this.expression instanceof AST_SymbolRef) return null; + default: + if (first_in_statement && is_iife_call(this.expression)) return this; + return this.expression.drop_side_effect_free(compressor, first_in_statement); + } + }); + def(AST_SymbolRef, function() { + return this.undeclared() ? this : null; + }); + def(AST_Object, function(compressor, first_in_statement){ + var values = trim(this.properties, compressor, first_in_statement); + return values && AST_Seq.from_array(values); + }); + def(AST_ObjectProperty, function(compressor, first_in_statement){ + return this.value.drop_side_effect_free(compressor, first_in_statement); + }); + def(AST_Array, function(compressor, first_in_statement){ + var values = trim(this.elements, compressor, first_in_statement); + return values && AST_Seq.from_array(values); + }); + def(AST_Dot, function(compressor, first_in_statement){ + if (!compressor.option("pure_getters")) return this; + return this.expression.drop_side_effect_free(compressor, first_in_statement); + }); + def(AST_Sub, function(compressor, first_in_statement){ + if (!compressor.option("pure_getters")) return this; + var expression = this.expression.drop_side_effect_free(compressor, first_in_statement); + if (!expression) return this.property.drop_side_effect_free(compressor, first_in_statement); + var property = this.property.drop_side_effect_free(compressor); + if (!property) return expression; + return make_node(AST_Seq, this, { + car: expression, + cdr: property + }); + }); + def(AST_Seq, function(compressor){ + var cdr = this.cdr.drop_side_effect_free(compressor); + if (cdr === this.cdr) return this; + if (!cdr) return this.car; + return make_node(AST_Seq, this, { + car: this.car, + cdr: cdr + }); + }); + })(function(node, func){ + node.DEFMETHOD("drop_side_effect_free", func); + }); + OPT(AST_SimpleStatement, function(self, compressor){ if (compressor.option("side_effects")) { - if (!self.body.has_side_effects(compressor)) { + var body = self.body; + if (!body.has_side_effects(compressor)) { compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); return make_node(AST_EmptyStatement, self); } + var node = body.drop_side_effect_free(compressor, true); + if (!node) { + compressor.warn("Dropping side-effect-free statement [{file}:{line},{col}]", self.start); + return make_node(AST_EmptyStatement, self); + } + if (node !== body) { + return make_node(AST_SimpleStatement, self, { body: node }); + } } return self; }); @@ -1749,8 +2052,14 @@ merge(Compressor.prototype, { extract_declarations_from_unreachable_code(compressor, self.body, a); return make_node(AST_BlockStatement, self, { body: a }); } + } else { + // self instanceof AST_Do + return self.body; } } + if (self instanceof AST_While) { + return make_node(AST_For, self, self).transform(compressor); + } return self; }); @@ -1799,16 +2108,6 @@ merge(Compressor.prototype, { } }; - OPT(AST_While, function(self, compressor) { - if (!compressor.option("loops")) return self; - self = AST_DWLoop.prototype.optimize.call(self, compressor); - if (self instanceof AST_While) { - if_break_in_loop(self, compressor); - self = make_node(AST_For, self, self).transform(compressor); - } - return self; - }); - OPT(AST_For, function(self, compressor){ var cond = self.condition; if (cond) { @@ -1838,6 +2137,8 @@ merge(Compressor.prototype, { }); OPT(AST_If, function(self, compressor){ + if (is_empty(self.alternative)) self.alternative = null; + if (!compressor.option("conditionals")) return self; // if condition can be statically determined, warn and drop // one of the blocks. note, statically determined implies @@ -1866,7 +2167,6 @@ merge(Compressor.prototype, { } } } - if (is_empty(self.alternative)) self.alternative = null; var negated = self.condition.negate(compressor); var self_condition_length = self.condition.print_to_string().length; var negated_length = negated.print_to_string().length; @@ -1890,8 +2190,8 @@ merge(Compressor.prototype, { return make_node(AST_SimpleStatement, self, { body: make_node(AST_Conditional, self, { condition : self.condition, - consequent : self.body.body, - alternative : self.alternative.body + consequent : statement_to_expression(self.body), + alternative : statement_to_expression(self.alternative) }) }).transform(compressor); } @@ -1907,14 +2207,14 @@ merge(Compressor.prototype, { body: make_node(AST_Binary, self, { operator : "||", left : negated, - right : self.body.body + right : statement_to_expression(self.body) }) }).transform(compressor); return make_node(AST_SimpleStatement, self, { body: make_node(AST_Binary, self, { operator : "&&", left : self.condition, - right : self.body.body + right : statement_to_expression(self.body) }) }).transform(compressor); } @@ -1925,7 +2225,7 @@ merge(Compressor.prototype, { body: make_node(AST_Binary, self, { operator : "||", left : self.condition, - right : self.alternative.body + right : statement_to_expression(self.alternative) }) }).transform(compressor); } @@ -2102,17 +2402,10 @@ merge(Compressor.prototype, { return self; }); - OPT(AST_Function, function(self, compressor){ - self = AST_Lambda.prototype.optimize.call(self, compressor); - if (compressor.option("unused") && !compressor.option("keep_fnames")) { - if (self.name && self.name.unreferenced()) { - self.name = null; - } - } - return self; - }); - OPT(AST_Call, function(self, compressor){ + self.args = self.args.map(function(arg) { + return arg.evaluate(compressor)[0]; + }); if (compressor.option("unsafe")) { var exp = self.expression; if (exp instanceof AST_SymbolRef && exp.undeclared()) { @@ -2225,39 +2518,57 @@ merge(Compressor.prototype, { }).transform(compressor); } else if (exp instanceof AST_Dot && exp.expression instanceof AST_Array && exp.property == "join") EXIT: { - var separator = self.args.length == 0 ? "," : self.args[0].evaluate(compressor)[1]; - if (separator == null) break EXIT; // not a constant - var elements = exp.expression.elements.reduce(function(a, el){ + var separator; + if (self.args.length > 0) { + separator = self.args[0].evaluate(compressor); + if (separator.length < 2) break EXIT; // not a constant + separator = separator[1]; + } + var elements = []; + var consts = []; + exp.expression.elements.forEach(function(el) { el = el.evaluate(compressor); - if (a.length == 0 || el.length == 1) { - a.push(el); + if (el.length > 1) { + consts.push(el[1]); } else { - var last = a[a.length - 1]; - if (last.length == 2) { - // it's a constant - var val = "" + last[1] + separator + el[1]; - a[a.length - 1] = [ make_node_from_constant(compressor, val, last[0]), val ]; - } else { - a.push(el); + if (consts.length > 0) { + elements.push(make_node(AST_String, self, { + value: consts.join(separator) + })); + consts.length = 0; } + elements.push(el[0]); } - return a; - }, []); + }); + if (consts.length > 0) { + elements.push(make_node(AST_String, self, { + value: consts.join(separator) + })); + } if (elements.length == 0) return make_node(AST_String, self, { value: "" }); - if (elements.length == 1) return elements[0][0]; + if (elements.length == 1) { + if (elements[0].is_string(compressor)) { + return elements[0]; + } + return make_node(AST_Binary, elements[0], { + operator : "+", + left : make_node(AST_String, self, { value: "" }), + right : elements[0] + }); + } if (separator == "") { var first; - if (elements[0][0] instanceof AST_String - || elements[1][0] instanceof AST_String) { - first = elements.shift()[0]; + if (elements[0].is_string(compressor) + || elements[1].is_string(compressor)) { + first = elements.shift(); } else { first = make_node(AST_String, self, { value: "" }); } return elements.reduce(function(prev, el){ - return make_node(AST_Binary, el[0], { + return make_node(AST_Binary, el, { operator : "+", left : prev, - right : el[0], + right : el }); }, first).transform(compressor); } @@ -2266,9 +2577,7 @@ merge(Compressor.prototype, { var node = self.clone(); node.expression = node.expression.clone(); node.expression.expression = node.expression.expression.clone(); - node.expression.expression.elements = elements.map(function(el){ - return el[0]; - }); + node.expression.expression.elements = elements; return best_of(self, node); } } @@ -2292,7 +2601,12 @@ merge(Compressor.prototype, { } } } - return self.evaluate(compressor)[0]; + if (compressor.option("negate_iife") + && compressor.parent() instanceof AST_SimpleStatement + && is_iife_call(self)) { + return self.negate(compressor, true); + } + return self; }); OPT(AST_New, function(self, compressor){ @@ -2315,9 +2629,8 @@ merge(Compressor.prototype, { OPT(AST_Seq, function(self, compressor){ if (!compressor.option("side_effects")) return self; - if (!self.car.has_side_effects(compressor)) { - return maintain_this_binding(compressor.parent(), self, self.cdr); - } + self.car = self.car.drop_side_effect_free(compressor, first_in_statement(compressor)); + if (!self.car) return maintain_this_binding(compressor.parent(), self, self.cdr); if (compressor.option("cascade")) { if (self.car instanceof AST_Assign && !self.car.left.has_side_effects(compressor)) { @@ -2379,6 +2692,10 @@ merge(Compressor.prototype, { // !!foo ==> foo, if we're in boolean context return e.expression; } + if (e instanceof AST_Binary) { + var statement = first_in_statement(compressor); + self = (statement ? best_of_statement : best_of)(self, e.negate(compressor, statement)); + } break; case "typeof": // typeof always returns a non-empty string, thus it's @@ -2392,9 +2709,6 @@ merge(Compressor.prototype, { } return make_node(AST_True, self); } - if (e instanceof AST_Binary && self.operator == "!") { - self = best_of(self, e.negate(compressor)); - } } return self.evaluate(compressor)[0]; }); @@ -2502,14 +2816,15 @@ merge(Compressor.prototype, { // XXX: intentionally falling down to the next case case "==": case "!=": + // "undefined" == typeof x => undefined === x if (self.left instanceof AST_String && self.left.value == "undefined" && self.right instanceof AST_UnaryPrefix - && self.right.operator == "typeof" - && compressor.option("unsafe")) { - if (!(self.right.expression instanceof AST_SymbolRef) - || !self.right.expression.undeclared()) { - self.right = self.right.expression; + && self.right.operator == "typeof") { + var expr = self.right.expression; + if (expr instanceof AST_SymbolRef ? !expr.undeclared() + : !(expr instanceof AST_PropAccess) || compressor.option("screw_ie8")) { + self.right = expr; self.left = make_node(AST_Undefined, self.left).optimize(compressor); if (self.operator.length == 2) self.operator += "="; } @@ -2570,11 +2885,12 @@ merge(Compressor.prototype, { if (compressor.option("comparisons") && self.is_boolean()) { if (!(compressor.parent() instanceof AST_Binary) || compressor.parent() instanceof AST_Assign) { + var statement = first_in_statement(compressor); var negated = make_node(AST_UnaryPrefix, self, { operator: "!", - expression: self.negate(compressor) + expression: self.negate(compressor, statement) }); - self = best_of(self, negated); + self = (statement ? best_of_statement : best_of)(self, negated); } if (compressor.option("unsafe_comps")) { switch (self.operator) { @@ -2670,9 +2986,16 @@ merge(Compressor.prototype, { } // x && (y && z) ==> x && y && z // x || (y || z) ==> x || y || z + // x + ("y" + z) ==> x + "y" + z + // "x" + (y + "z")==> "x" + y + "z" if (self.right instanceof AST_Binary && self.right.operator == self.operator - && (self.operator == "&&" || self.operator == "||")) + && (self.operator == "&&" + || self.operator == "||" + || (self.operator == "+" + && (self.right.left.is_string(compressor) + || (self.left.is_string(compressor) + && self.right.right.is_string(compressor)))))) { self.left = make_node(AST_Binary, self.left, { operator : self.operator, @@ -2686,21 +3009,34 @@ merge(Compressor.prototype, { }); OPT(AST_SymbolRef, function(self, compressor){ - if (self.undeclared() && !isLHS(self, compressor.parent())) { - var defines = compressor.option("global_defs"); - if (defines && HOP(defines, self.name)) { - return make_node_from_constant(compressor, defines[self.name], self); - } - // testing against !self.scope.uses_with first is an optimization - if (!self.scope.uses_with || !compressor.find_parent(AST_With)) { - switch (self.name) { - case "undefined": - return make_node(AST_Undefined, self); - case "NaN": - return make_node(AST_NaN, self).transform(compressor); - case "Infinity": - return make_node(AST_Infinity, self).transform(compressor); - } + var def = self.resolve_defines(compressor); + if (def) { + return def; + } + // testing against !self.scope.uses_with first is an optimization + if (self.undeclared() && !isLHS(self, compressor.parent()) + && (!self.scope.uses_with || !compressor.find_parent(AST_With))) { + switch (self.name) { + case "undefined": + return make_node(AST_Undefined, self); + case "NaN": + return make_node(AST_NaN, self).transform(compressor); + case "Infinity": + return make_node(AST_Infinity, self).transform(compressor); + } + } + if (compressor.option("evaluate") + && compressor.option("reduce_vars") + && !isLHS(self, compressor.parent())) { + var d = self.definition(); + if (d.constant && !d.modified && d.init && d.init.is_constant(compressor)) { + var original_as_string = self.print_to_string(); + var const_node = make_node_from_constant(compressor, d.init.constant_value(compressor), self); + var const_node_as_string = const_node.print_to_string(); + var per_const_overhead = d.global || !d.references.length ? 0 + : (d.name.length + 2 + const_node_as_string.length) / d.references.length; + if (const_node_as_string.length <= original_as_string.length + per_const_overhead) + return const_node; } } return self; @@ -2719,13 +3055,11 @@ merge(Compressor.prototype, { var scope = compressor.find_parent(AST_Scope); var undef = scope.find_variable("undefined"); if (undef) { - var ref = make_node(AST_SymbolRef, self, { + return make_node(AST_SymbolRef, self, { name : "undefined", scope : scope, thedef : undef }); - ref.reference(); - return ref; } } return self; @@ -2773,8 +3107,9 @@ merge(Compressor.prototype, { return maintain_this_binding(compressor.parent(), self, self.alternative); } } - var negated = cond[0].negate(compressor); - if (best_of(cond[0], negated) === negated) { + var statement = first_in_statement(compressor); + var negated = cond[0].negate(compressor, statement); + if ((statement ? best_of_statement : best_of)(cond[0], negated) === negated) { self = make_node(AST_Conditional, self, { condition: negated, consequent: self.alternative, @@ -2967,6 +3302,10 @@ merge(Compressor.prototype, { }); OPT(AST_Dot, function(self, compressor){ + var def = self.resolve_defines(compressor); + if (def) { + return def; + } var prop = self.property; if (RESERVED_WORDS(prop) && !compressor.option("screw_ie8")) { return make_node(AST_Sub, self, { @@ -2976,6 +3315,28 @@ merge(Compressor.prototype, { }) }).optimize(compressor); } + if (compressor.option("unsafe_proto") + && self.expression instanceof AST_Dot + && self.expression.property == "prototype") { + var exp = self.expression.expression; + if (exp instanceof AST_SymbolRef && exp.undeclared()) switch (exp.name) { + case "Array": + self.expression = make_node(AST_Array, self.expression, { + elements: [] + }); + break; + case "Object": + self.expression = make_node(AST_Object, self.expression, { + properties: [] + }); + break; + case "String": + self.expression = make_node(AST_String, self.expression, { + value: "" + }); + break; + } + } return self.evaluate(compressor)[0]; }); @@ -2996,4 +3357,12 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_VarDef, function(self, compressor){ + var defines = compressor.option("global_defs"); + if (defines && HOP(defines, self.name.name)) { + compressor.warn('global_defs ' + self.name.name + ' redefined [{file}:{line},{col}]', self.start); + } + return self; + }); + })(); diff --git a/lib/output.js b/lib/output.js index 50e5aa43..4a0a1e0e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -70,7 +70,7 @@ function OutputStream(options) { unescape_regexps : false, inline_script : false, width : 80, - max_line_len : 32000, + max_line_len : false, beautify : false, source_map : null, bracketize : false, @@ -198,16 +198,29 @@ function OutputStream(options) { var might_need_space = false; var might_need_semicolon = false; + var might_add_newline = 0; var last = null; function last_char() { return last.charAt(last.length - 1); }; - function maybe_newline() { - if (options.max_line_len && current_col > options.max_line_len) - print("\n"); - }; + var ensure_line_len = options.max_line_len ? function() { + if (current_col > options.max_line_len) { + if (might_add_newline) { + var left = OUTPUT.slice(0, might_add_newline); + var right = OUTPUT.slice(might_add_newline); + OUTPUT = left + "\n" + right; + current_line++; + current_pos++; + current_col = right.length; + } + if (current_col > options.max_line_len) { + AST_Node.warn("Output exceeds {max_line_len} characters", options); + } + } + might_add_newline = 0; + } : noop; var requireSemicolonChars = makePredicate("( [ + * / - , ."); @@ -223,6 +236,7 @@ function OutputStream(options) { current_col++; current_pos++; } else { + ensure_line_len(); OUTPUT += "\n"; current_pos++; current_line++; @@ -243,6 +257,7 @@ function OutputStream(options) { if (!options.beautify && options.preserve_line && stack[stack.length - 1]) { var target_line = stack[stack.length - 1].start.line; while (current_line < target_line) { + ensure_line_len(); OUTPUT += "\n"; current_pos++; current_line++; @@ -254,8 +269,9 @@ function OutputStream(options) { if (might_need_space) { var prev = last_char(); if ((is_identifier_char(prev) - && (is_identifier_char(ch) || ch == "\\")) - || (/^[\+\-\/]$/.test(ch) && ch == prev)) + && (is_identifier_char(ch) || ch == "\\")) + || (ch == "/" && ch == prev) + || ((ch == "+" || ch == "-") && ch == last)) { OUTPUT += " "; current_col++; @@ -263,16 +279,16 @@ function OutputStream(options) { } might_need_space = false; } + OUTPUT += str; + current_pos += str.length; var a = str.split(/\r?\n/), n = a.length - 1; current_line += n; - if (n == 0) { - current_col += a[n].length; - } else { + current_col += a[0].length; + if (n > 0) { + ensure_line_len(); current_col = a[n].length; } - current_pos += str.length; last = str; - OUTPUT += str; }; var space = options.beautify ? function() { @@ -298,7 +314,10 @@ function OutputStream(options) { var newline = options.beautify ? function() { print("\n"); - } : maybe_newline; + } : options.max_line_len ? function() { + ensure_line_len(); + might_add_newline = OUTPUT.length; + } : noop; var semicolon = options.beautify ? function() { print(";"); @@ -375,6 +394,9 @@ function OutputStream(options) { } : noop; function get() { + if (might_add_newline) { + ensure_line_len(); + } return OUTPUT; }; @@ -425,7 +447,6 @@ function OutputStream(options) { pos : function() { return current_pos }, push_node : function(node) { stack.push(node) }, pop_node : function() { return stack.pop() }, - stack : function() { return stack }, parent : function(n) { return stack[stack.length - 2 - (n || 0)]; } @@ -939,7 +960,10 @@ function OutputStream(options) { output.space(); output.print("else"); output.space(); - force_statement(self.alternative, output); + if (self.alternative instanceof AST_If) + self.alternative.print(output); + else + force_statement(self.alternative, output); } else { self._do_print_body(output); } @@ -1334,30 +1358,6 @@ function OutputStream(options) { } }; - // return true if the node at the top of the stack (that means the - // innermost node in the current output) is lexically the first in - // a statement. - function first_in_statement(output) { - var a = output.stack(), i = a.length, node = a[--i], p = a[--i]; - while (i > 0) { - if (p instanceof AST_Statement && p.body === node) - return true; - if ((p instanceof AST_Seq && p.car === node ) || - (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || - (p instanceof AST_Dot && p.expression === node ) || - (p instanceof AST_Sub && p.expression === node ) || - (p instanceof AST_Conditional && p.condition === node ) || - (p instanceof AST_Binary && p.left === node ) || - (p instanceof AST_UnaryPostfix && p.expression === node )) - { - node = p; - p = a[--i]; - } else { - return false; - } - } - }; - // self should be AST_New. decide if we want to show parens or not. function need_constructor_parens(self, output) { // Always print parentheses with arguments diff --git a/lib/parse.js b/lib/parse.js index ec82d47d..37f06df7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1308,6 +1308,10 @@ function parse($TEXT, options) { }); }); + var create_accessor = embed_tokens(function() { + return function_(AST_Accessor); + }); + var object_ = embed_tokens(function() { expect("{"); var first = true, a = []; @@ -1324,7 +1328,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectGetter({ start : start, key : as_atom_node(), - value : function_(AST_Accessor), + value : create_accessor(), end : prev() })); continue; @@ -1333,7 +1337,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectSetter({ start : start, key : as_atom_node(), - value : function_(AST_Accessor), + value : create_accessor(), end : prev() })); continue; diff --git a/lib/scope.js b/lib/scope.js index 55d1eff1..29e4103a 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -97,26 +97,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var scope = self.parent_scope = null; var labels = new Dictionary(); var defun = null; - var last_var_had_const_pragma = false; - var nesting = 0; var tw = new TreeWalker(function(node, descend){ if (options.screw_ie8 && node instanceof AST_Catch) { var save_scope = scope; scope = new AST_Scope(node); - scope.init_scope_vars(nesting); + scope.init_scope_vars(); scope.parent_scope = save_scope; descend(); scope = save_scope; return true; } if (node instanceof AST_Scope) { - node.init_scope_vars(nesting); + node.init_scope_vars(); var save_scope = node.parent_scope = scope; var save_defun = defun; var save_labels = labels; defun = scope = node; labels = new Dictionary(); - ++nesting; descend(); --nesting; + descend(); scope = save_scope; defun = save_defun; labels = save_labels; @@ -155,13 +153,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // later. (node.scope = defun.parent_scope).def_function(node); } - else if (node instanceof AST_Var) { - last_var_had_const_pragma = node.has_const_pragma(); - } else if (node instanceof AST_SymbolVar || node instanceof AST_SymbolConst) { var def = defun.def_variable(node); - def.constant = node instanceof AST_SymbolConst || last_var_had_const_pragma; + def.constant = node instanceof AST_SymbolConst; def.init = tw.parent().value; } else if (node instanceof AST_SymbolCatch) { @@ -184,17 +179,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var func = null; var globals = self.globals = new Dictionary(); var tw = new TreeWalker(function(node, descend){ - function isModified(node, level) { - var parent = tw.parent(level); - if (parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") - || parent instanceof AST_Assign && parent.left === node - || parent instanceof AST_Call && parent.expression === node) { - return true; - } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return isModified(parent, level + 1); - } - } - if (node instanceof AST_Lambda) { var prev_func = func; func = node; @@ -218,21 +202,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.scope.uses_arguments = true; } if (!sym) { - var g; - if (globals.has(name)) { - g = globals.get(name); - } else { - g = new SymbolDef(self, globals.size(), node); - g.undeclared = true; - g.global = true; - globals.set(name, g); - } - sym = g; + sym = self.def_global(node); } node.thedef = sym; - if (isModified(node, 0)) { - sym.modified = true; - } node.reference(options); return true; } @@ -244,7 +216,20 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } }); -AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ +AST_Toplevel.DEFMETHOD("def_global", function(node){ + var globals = this.globals, name = node.name; + if (globals.has(name)) { + return globals.get(name); + } else { + var g = new SymbolDef(this, globals.size(), node); + g.undeclared = true; + g.global = true; + globals.set(name, g); + return g; + } +}); + +AST_Scope.DEFMETHOD("init_scope_vars", function(){ this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement @@ -252,7 +237,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.parent_scope = null; // the parent scope this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes this.cname = -1; // the current index for mangling functions/variables - this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); AST_Lambda.DEFMETHOD("init_scope_vars", function(){ @@ -270,15 +254,14 @@ AST_SymbolRef.DEFMETHOD("reference", function(options) { var s = this.scope; while (s) { push_uniq(s.enclosed, def); - if (s === def.scope) break; if (options.keep_fnames) { - s.variables.each(function(d) { + s.functions.each(function(d) { push_uniq(def.scope.enclosed, d); }); } + if (s === def.scope) break; s = s.parent_scope; } - this.frame = this.scope.nesting - def.scope.nesting; }); AST_Scope.DEFMETHOD("find_variable", function(name){ @@ -382,12 +365,6 @@ AST_Symbol.DEFMETHOD("global", function(){ return this.definition().global; }); -AST_Var.DEFMETHOD("has_const_pragma", function() { - var comments_before = this.start && this.start.comments_before; - var lastComment = comments_before && comments_before[comments_before.length - 1]; - return lastComment && /@const\b/.test(lastComment.value); -}); - AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ return defaults(options, { except : [], diff --git a/lib/utils.js b/lib/utils.js index d0a21ac9..a0571d65 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -320,3 +320,26 @@ Dictionary.fromObject = function(obj) { function HOP(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } + +// return true if the node at the top of the stack (that means the +// innermost node in the current output) is lexically the first in +// a statement. +function first_in_statement(stack) { + var node = stack.parent(-1); + for (var i = 0, p; p = stack.parent(i); i++) { + if (p instanceof AST_Statement && p.body === node) + return true; + if ((p instanceof AST_Seq && p.car === node ) || + (p instanceof AST_Call && p.expression === node && !(p instanceof AST_New) ) || + (p instanceof AST_Dot && p.expression === node ) || + (p instanceof AST_Sub && p.expression === node ) || + (p instanceof AST_Conditional && p.condition === node ) || + (p instanceof AST_Binary && p.left === node ) || + (p instanceof AST_UnaryPostfix && p.expression === node )) + { + node = p; + } else { + return false; + } + } +} diff --git a/test/benchmark.js b/test/benchmark.js new file mode 100644 index 00000000..dc176a88 --- /dev/null +++ b/test/benchmark.js @@ -0,0 +1,49 @@ +#! /usr/bin/env node +// -*- js -*- + +"use strict"; + +var createHash = require("crypto").createHash; +var fork = require("child_process").fork; +var args = process.argv.slice(2); +if (!args.length) { + args.push("-mc", "warnings=false"); +} +args.push("--stats"); +var urls = [ + "https://code.jquery.com/jquery-3.1.1.js", + "https://code.angularjs.org/1.6.1/angular.js", + "https://cdnjs.cloudflare.com/ajax/libs/mathjs/3.9.0/math.js", + "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js", + "https://unpkg.com/react@15.3.2/dist/react.js", + "http://builds.emberjs.com/tags/v2.11.0/ember.prod.js", + "https://cdn.jsdelivr.net/lodash/4.17.4/lodash.js", + "https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.js", +]; +var results = {}; +var remaining = 2 * urls.length; +function done() { + if (!--remaining) { + urls.forEach(function(url) { + console.log(); + console.log(url); + console.log(results[url].time); + console.log("SHA1:", results[url].sha1); + }); + } +} +urls.forEach(function(url) { + results[url] = { time: "" }; + require(url.slice(0, url.indexOf(":"))).get(url, function(res) { + var uglifyjs = fork("bin/uglifyjs", args, { silent: true }); + res.pipe(uglifyjs.stdin); + uglifyjs.stdout.pipe(createHash("sha1")).on("data", function(data) { + results[url].sha1 = data.toString("hex"); + done(); + }); + uglifyjs.stderr.setEncoding("utf8"); + uglifyjs.stderr.on("data", function(data) { + results[url].time += data; + }).on("end", done) + }); +}); diff --git a/test/compress/arrays.js b/test/compress/arrays.js index 77ef761a..f0ded06c 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -21,10 +21,19 @@ constant_join: { input: { var a = [ "foo", "bar", "baz" ].join(""); var a1 = [ "foo", "bar", "baz" ].join(); + var a2 = [ "foo", "bar", "baz" ].join(null); + var a3 = [ "foo", "bar", "baz" ].join(void 0); + var a4 = [ "foo", , "baz" ].join(); + var a5 = [ "foo", null, "baz" ].join(); + var a6 = [ "foo", void 0, "baz" ].join(); var b = [ "foo", 1, 2, 3, "bar" ].join(""); var c = [ boo(), "foo", 1, 2, 3, "bar", bar() ].join(""); var c1 = [ boo(), bar(), "foo", 1, 2, 3, "bar", bar() ].join(""); var c2 = [ 1, 2, "foo", "bar", baz() ].join(""); + var c3 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join(""); + var c4 = [ 1, 2, null, undefined, "foo", "bar", baz() ].join(""); + var c5 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join(); + var c6 = [ 1, 2, null, undefined, "foo", "bar", baz() ].join(); var d = [ "foo", 1 + 2 + "bar", "baz" ].join("-"); var e = [].join(foo + bar); var f = [].join(""); @@ -33,10 +42,19 @@ constant_join: { expect: { var a = "foobarbaz"; var a1 = "foo,bar,baz"; + var a2 = "foonullbarnullbaz"; + var a3 = "foo,bar,baz"; + var a4 = "foo,,baz"; + var a5 = "foo,,baz"; + var a6 = "foo,,baz"; var b = "foo123bar"; var c = boo() + "foo123bar" + bar(); var c1 = "" + boo() + bar() + "foo123bar" + bar(); var c2 = "12foobar" + baz(); + var c3 = boo() + bar() + "foo123bar" + bar() + "foo"; + var c4 = "12foobar" + baz(); + var c5 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join(); + var c6 = [ "1,2,,,foo,bar", baz() ].join(); var d = "foo-3bar-baz"; var e = [].join(foo + bar); var f = ""; @@ -73,6 +91,41 @@ constant_join_2: { } } +constant_join_3: { + options = { + unsafe: true, + evaluate: true, + }; + input: { + var a = [ null ].join(); + var b = [ , ].join(); + var c = [ , 1, , 3 ].join(); + var d = [ foo ].join(); + var e = [ foo, null, undefined, bar ].join("-"); + var f = [ foo, bar ].join(""); + var g = [ null, "foo", null, bar + "baz" ].join(""); + var h = [ null, "foo", null, bar + "baz" ].join("-"); + var i = [ "foo" + bar, null, baz + "moo" ].join(""); + var j = [ foo + "bar", baz ].join(""); + var k = [ foo, "bar" + baz ].join(""); + var l = [ foo, bar + "baz" ].join(""); + } + expect: { + var a = ""; + var b = ""; + var c = ",1,,3"; + var d = "" + foo; + var e = [ foo, "-", bar ].join("-"); + var f = "" + foo + bar; + var g = "foo" + bar + "baz"; + var h = [ "-foo-", bar + "baz" ].join("-"); + var i = "foo" + bar + baz + "moo"; + var j = foo + "bar" + baz; + var k = foo + "bar" + baz; + var l = foo + (bar + "baz"); + } +} + for_loop: { options = { unsafe : true, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index ef7af9ed..d7432f3f 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -338,8 +338,9 @@ collapse_vars_while: { 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:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects: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, + side_effects:true } input: { function f1(y) { @@ -409,6 +410,79 @@ collapse_vars_do_while: { } } +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 + } + input: { + function f1(y) { + // The constant do-while condition `c` will be replaced. + var c = 9; + do { } while (c === 77); + } + function f2(y) { + // The non-constant do-while condition `c` will not be replaced. + var c = 5 - y; + do { } while (c); + } + function f3(y) { + // The constant `x` will be replaced in the do loop body. + function fn(n) { console.log(n); } + var a = 2, x = 7; + do { + fn(a = x); + break; + } while (y); + } + function f4(y) { + // The non-constant `a` will not be replaced in the do loop body. + var a = y / 4; + do { + return a; + } while (y); + } + function f5(y) { + function p(x) { console.log(x); } + do { + // The non-constant `a` will be replaced in p(a) + // because it is declared in same block. + var a = y - 3; + p(a); + } while (--y); + } + } + expect: { + function f1(y) { + do ; while (false); + } + function f2(y) { + var c = 5 - y; + do ; while (c); + } + function f3(y) { + function fn(n) { console.log(n); } + do { + fn(7); + break; + } while (y); + } + function f4(y) { + var a = y / 4; + do + return a; + while (y); + } + function f5(y) { + function p(x) { console.log(x); } + do { + p(y - 3); + } while (--y); + } + } +} + collapse_vars_seq: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, @@ -567,8 +641,9 @@ collapse_vars_assignment: { collapse_vars_lvalues: { 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 + 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, + side_effects:true } input: { function f0(x) { var i = ++x; return x += i; } @@ -593,7 +668,38 @@ collapse_vars_lvalues: { function f7(x) { var w = e1(), v = e2(), c = v - x; return (w = x) - c; } function f8(x) { var w = e1(), v = e2(); return (w = x) - (v - x); } function f9(x) { var w = e1(); return e2() - x - (w = x); } + } +} +collapse_vars_lvalues_drop_assign: { + 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 + } + 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; } + function f3(x) { var a = (x -= 3), b = x + a; return b; } + function f4(x) { var a = (x -= 3); return x + a; } + function f5(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return b - c; } + function f6(x) { var w = e1(), v = e2(), c = v = --x, b = w = x; return c - b; } + function f7(x) { var w = e1(), v = e2(), c = v - x, b = w = x; return b - c; } + function f8(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return b - c; } + function f9(x) { var w = e1(), v = e2(), b = w = x, c = v - x; return c - b; } + } + 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; } + function f3(x) { var a = (x -= 3); return x + a; } + function f4(x) { var a = (x -= 3); return x + a; } + function f5(x) { var v = (e1(), e2()), c = v = --x; return x - c; } + function f6(x) { e1(), e2(); return --x - x; } + function f7(x) { var v = (e1(), e2()), c = v - x; return x - c; } + function f8(x) { var v = (e1(), e2()); return x - (v - x); } + function f9(x) { e1(); return e2() - x - x; } } } diff --git a/test/compress/concat-strings.js b/test/compress/concat-strings.js index 50eef8b8..d2503c6d 100644 --- a/test/compress/concat-strings.js +++ b/test/compress/concat-strings.js @@ -24,3 +24,143 @@ concat_1: { var f = "\x00360\08\0"; } } + +concat_2: { + options = {}; + input: { + console.log( + 1 + (2 + 3), + 1 + (2 + "3"), + 1 + ("2" + 3), + 1 + ("2" + "3"), + "1" + (2 + 3), + "1" + (2 + "3"), + "1" + ("2" + 3), + "1" + ("2" + "3") + ); + } + expect: { + console.log( + 1 + (2 + 3), + 1 + (2 + "3"), + 1 + "2" + 3, + 1 + "2" + "3", + "1" + (2 + 3), + "1" + 2 + "3", + "1" + "2" + 3, + "1" + "2" + "3" + ); + } +} + +concat_3: { + options = {}; + input: { + console.log( + 1 + 2 + (3 + 4 + 5), + 1 + 2 + (3 + 4 + "5"), + 1 + 2 + (3 + "4" + 5), + 1 + 2 + (3 + "4" + "5"), + 1 + 2 + ("3" + 4 + 5), + 1 + 2 + ("3" + 4 + "5"), + 1 + 2 + ("3" + "4" + 5), + 1 + 2 + ("3" + "4" + "5") + ); + } + expect: { + console.log( + 1 + 2 + (3 + 4 + 5), + 1 + 2 + (3 + 4 + "5"), + 1 + 2 + (3 + "4") + 5, + 1 + 2 + (3 + "4") + "5", + 1 + 2 + "3" + 4 + 5, + 1 + 2 + "3" + 4 + "5", + 1 + 2 + "3" + "4" + 5, + 1 + 2 + "3" + "4" + "5" + ); + } +} + +concat_4: { + options = {}; + input: { + console.log( + 1 + "2" + (3 + 4 + 5), + 1 + "2" + (3 + 4 + "5"), + 1 + "2" + (3 + "4" + 5), + 1 + "2" + (3 + "4" + "5"), + 1 + "2" + ("3" + 4 + 5), + 1 + "2" + ("3" + 4 + "5"), + 1 + "2" + ("3" + "4" + 5), + 1 + "2" + ("3" + "4" + "5") + ); + } + expect: { + console.log( + 1 + "2" + (3 + 4 + 5), + 1 + "2" + (3 + 4) + "5", + 1 + "2" + 3 + "4" + 5, + 1 + "2" + 3 + "4" + "5", + 1 + "2" + "3" + 4 + 5, + 1 + "2" + "3" + 4 + "5", + 1 + "2" + "3" + "4" + 5, + 1 + "2" + "3" + "4" + "5" + ); + } +} + +concat_5: { + options = {}; + input: { + console.log( + "1" + 2 + (3 + 4 + 5), + "1" + 2 + (3 + 4 + "5"), + "1" + 2 + (3 + "4" + 5), + "1" + 2 + (3 + "4" + "5"), + "1" + 2 + ("3" + 4 + 5), + "1" + 2 + ("3" + 4 + "5"), + "1" + 2 + ("3" + "4" + 5), + "1" + 2 + ("3" + "4" + "5") + ); + } + expect: { + console.log( + "1" + 2 + (3 + 4 + 5), + "1" + 2 + (3 + 4) + "5", + "1" + 2 + 3 + "4" + 5, + "1" + 2 + 3 + "4" + "5", + "1" + 2 + "3" + 4 + 5, + "1" + 2 + "3" + 4 + "5", + "1" + 2 + "3" + "4" + 5, + "1" + 2 + "3" + "4" + "5" + ); + } +} + +concat_6: { + options = {}; + input: { + console.log( + "1" + "2" + (3 + 4 + 5), + "1" + "2" + (3 + 4 + "5"), + "1" + "2" + (3 + "4" + 5), + "1" + "2" + (3 + "4" + "5"), + "1" + "2" + ("3" + 4 + 5), + "1" + "2" + ("3" + 4 + "5"), + "1" + "2" + ("3" + "4" + 5), + "1" + "2" + ("3" + "4" + "5") + ); + } + expect: { + console.log( + "1" + "2" + (3 + 4 + 5), + "1" + "2" + (3 + 4) + "5", + "1" + "2" + 3 + "4" + 5, + "1" + "2" + 3 + "4" + "5", + "1" + "2" + "3" + 4 + 5, + "1" + "2" + "3" + 4 + "5", + "1" + "2" + "3" + "4" + 5, + "1" + "2" + "3" + "4" + "5" + ); + } +} diff --git a/test/compress/const.js b/test/compress/const.js new file mode 100644 index 00000000..f1f13f49 --- /dev/null +++ b/test/compress/const.js @@ -0,0 +1,165 @@ +issue_1191: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function foo(rot) { + const rotTol = 5; + if (rot < -rotTol || rot > rotTol) + bar(); + baz(); + } + } + expect: { + function foo(rot) { + (rot < -5 || rot > 5) && bar(); + baz(); + } + } +} + +issue_1194: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function f1() {const a = "X"; return a + a;} + function f2() {const aa = "X"; return aa + aa;} + function f3() {const aaa = "X"; return aaa + aaa;} + } + expect: { + function f1(){return"XX"} + function f2(){return"XX"} + function f3(){return"XX"} + } +} + +issue_1396: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + function foo(a) { + const VALUE = 1; + console.log(2 | VALUE); + console.log(VALUE + 1); + console.log(VALUE); + console.log(a & VALUE); + } + function bar() { + const s = "01234567890123456789"; + console.log(s + s + s + s + s); + + const CONSTANT = "abc"; + console.log(CONSTANT + CONSTANT + CONSTANT + CONSTANT + CONSTANT); + } + } + expect: { + function foo(a) { + console.log(3); + console.log(2); + console.log(1); + console.log(1 & a); + } + function bar() { + const s = "01234567890123456789"; + console.log(s + s + s + s + s); + + console.log("abcabcabcabcabc"); + } + } +} + +unused_regexp_literal: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + } + input: { + function f(){ var a = /b/; } + } + expect: { + function f(){} + } +} + +regexp_literal_not_const: { + options = { + evaluate : true, + booleans : true, + comparisons : true, + dead_code : true, + conditionals : true, + side_effects : true, + unused : true, + hoist_funs : true, + if_return : true, + join_vars : true, + sequences : false, + collapse_vars : false, + reduce_vars : true, + } + input: { + (function(){ + var result; + const s = 'acdabcdeabbb'; + const REGEXP_LITERAL = /ab*/g; + while (result = REGEXP_LITERAL.exec(s)) { + console.log(result[0]); + } + })(); + } + expect: { + (function() { + var result; + const REGEXP_LITERAL = /ab*/g; + while (result = REGEXP_LITERAL.exec("acdabcdeabbb")) console.log(result[0]); + })(); + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index fa4b37d6..2596e80e 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -1,206 +1,208 @@ -dead_code_1: {
- options = {
- dead_code: true
- };
- input: {
- function f() {
- a();
- b();
- x = 10;
- return;
- if (x) {
- y();
- }
- }
- }
- expect: {
- function f() {
- a();
- b();
- x = 10;
- return;
- }
- }
-}
-
-dead_code_2_should_warn: {
- options = {
- dead_code: true
- };
- input: {
- function f() {
- g();
- x = 10;
- throw "foo";
- // completely discarding the `if` would introduce some
- // bugs. UglifyJS v1 doesn't deal with this issue; in v2
- // we copy any declarations to the upper scope.
- if (x) {
- y();
- var x;
- function g(){};
- // but nested declarations should not be kept.
- (function(){
- var q;
- function y(){};
- })();
- }
- }
- }
- expect: {
- function f() {
- g();
- x = 10;
- throw "foo";
- var x;
- function g(){};
- }
- }
-}
-
-dead_code_constant_boolean_should_warn_more: {
- options = {
- dead_code : true,
- loops : true,
- booleans : true,
- conditionals : true,
- evaluate : true
- };
- input: {
- while (!((foo && bar) || (x + "0"))) {
- console.log("unreachable");
- var foo;
- function bar() {}
- }
- for (var x = 10, y; x && (y || x) && (!typeof x); ++x) {
- asdf();
- foo();
- var moo;
- }
- }
- expect: {
- var foo;
- function bar() {}
- // nothing for the while
- // as for the for, it should keep:
- var x = 10, y;
- var moo;
- }
-}
-
-dead_code_const_declaration: {
- options = {
- dead_code : true,
- loops : true,
- booleans : true,
- conditionals : true,
- evaluate : true
- };
- input: {
- var unused;
- const CONST_FOO = false;
- if (CONST_FOO) {
- console.log("unreachable");
- var moo;
- function bar() {}
- }
- }
- expect: {
- var unused;
- const CONST_FOO = !1;
- var moo;
- function bar() {}
- }
-}
-
-dead_code_const_annotation: {
- options = {
- dead_code : true,
- loops : true,
- booleans : true,
- conditionals : true,
- evaluate : true
- };
- input: {
- var unused;
- /** @const */ var CONST_FOO_ANN = false;
- if (CONST_FOO_ANN) {
- console.log("unreachable");
- var moo;
- function bar() {}
- }
- }
- expect: {
- var unused;
- var CONST_FOO_ANN = !1;
- var moo;
- function bar() {}
- }
-}
-
-dead_code_const_annotation_regex: {
- options = {
- dead_code : true,
- loops : true,
- booleans : true,
- conditionals : true,
- evaluate : true
- };
- input: {
- var unused;
- // @constraint this shouldn't be a constant
- var CONST_FOO_ANN = false;
- if (CONST_FOO_ANN) {
- console.log("reachable");
- }
- }
- expect: {
- var unused;
- var CONST_FOO_ANN = !1;
- CONST_FOO_ANN && console.log('reachable');
- }
-}
-
-dead_code_const_annotation_complex_scope: {
- options = {
- dead_code : true,
- loops : true,
- booleans : true,
- conditionals : true,
- evaluate : true
- };
- input: {
- var unused_var;
- /** @const */ var test = 'test';
- // @const
- var CONST_FOO_ANN = false;
- var unused_var_2;
- if (CONST_FOO_ANN) {
- console.log("unreachable");
- var moo;
- function bar() {}
- }
- if (test === 'test') {
- var beef = 'good';
- /** @const */ var meat = 'beef';
- var pork = 'bad';
- if (meat === 'pork') {
- console.log('also unreachable');
- } else if (pork === 'good') {
- console.log('reached, not const');
- }
- }
- }
- expect: {
- var unused_var;
- var test = 'test';
- var CONST_FOO_ANN = !1;
- var unused_var_2;
- var moo;
- function bar() {}
- var beef = 'good';
- var meat = 'beef';
- var pork = 'bad';
- 'good' === pork && console.log('reached, not const');
- }
-}
+dead_code_1: { + options = { + dead_code: true + }; + input: { + function f() { + a(); + b(); + x = 10; + return; + if (x) { + y(); + } + } + } + expect: { + function f() { + a(); + b(); + x = 10; + return; + } + } +} + +dead_code_2_should_warn: { + options = { + dead_code: true + }; + input: { + function f() { + g(); + x = 10; + throw "foo"; + // completely discarding the `if` would introduce some + // bugs. UglifyJS v1 doesn't deal with this issue; in v2 + // we copy any declarations to the upper scope. + if (x) { + y(); + var x; + function g(){}; + // but nested declarations should not be kept. + (function(){ + var q; + function y(){}; + })(); + } + } + } + expect: { + function f() { + g(); + x = 10; + throw "foo"; + var x; + function g(){}; + } + } +} + +dead_code_constant_boolean_should_warn_more: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + while (!((foo && bar) || (x + "0"))) { + console.log("unreachable"); + var foo; + function bar() {} + } + for (var x = 10, y; x && (y || x) && (!typeof x); ++x) { + asdf(); + foo(); + var moo; + } + } + expect: { + var foo; + function bar() {} + // nothing for the while + // as for the for, it should keep: + var x = 10, y; + var moo; + } +} + +dead_code_const_declaration: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + }; + input: { + var unused; + const CONST_FOO = false; + if (CONST_FOO) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + const CONST_FOO = !1; + var moo; + function bar() {} + } +} + +dead_code_const_annotation: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + }; + input: { + var unused; + /** @const */ var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + var moo; + function bar() {} + } +} + +dead_code_const_annotation_regex: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true + }; + input: { + var unused; + // @constraint this shouldn't be a constant + var CONST_FOO_ANN = false; + if (CONST_FOO_ANN) { + console.log("reachable"); + } + } + expect: { + var unused; + var CONST_FOO_ANN = !1; + CONST_FOO_ANN && console.log('reachable'); + } +} + +dead_code_const_annotation_complex_scope: { + options = { + dead_code : true, + loops : true, + booleans : true, + conditionals : true, + evaluate : true, + reduce_vars : true, + }; + input: { + var unused_var; + /** @const */ var test = 'test'; + // @const + var CONST_FOO_ANN = false; + var unused_var_2; + if (CONST_FOO_ANN) { + console.log("unreachable"); + var moo; + function bar() {} + } + if (test === 'test') { + var beef = 'good'; + /** @const */ var meat = 'beef'; + var pork = 'bad'; + if (meat === 'pork') { + console.log('also unreachable'); + } else if (pork === 'good') { + console.log('reached, not const'); + } + } + } + expect: { + var unused_var; + var test = 'test'; + var CONST_FOO_ANN = !1; + var unused_var_2; + var moo; + function bar() {} + var beef = 'good'; + var meat = 'beef'; + var pork = 'bad'; + } +} diff --git a/test/compress/drop-console.js b/test/compress/drop-console.js index 162b339c..7df6d9dc 100644 --- a/test/compress/drop-console.js +++ b/test/compress/drop-console.js @@ -1,24 +1,24 @@ -drop_console_1: {
- options = {};
- input: {
- console.log('foo');
- console.log.apply(console, arguments);
- }
- expect: {
- console.log('foo');
- console.log.apply(console, arguments);
- }
-}
-
-drop_console_1: {
- options = { drop_console: true };
- input: {
- console.log('foo');
- console.log.apply(console, arguments);
- }
- expect: {
- // with regular compression these will be stripped out as well
- void 0;
- void 0;
- }
-}
+drop_console_1: { + options = {}; + input: { + console.log('foo'); + console.log.apply(console, arguments); + } + expect: { + console.log('foo'); + console.log.apply(console, arguments); + } +} + +drop_console_2: { + options = { drop_console: true }; + input: { + console.log('foo'); + console.log.apply(console, arguments); + } + expect: { + // with regular compression these will be stripped out as well + void 0; + void 0; + } +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 035a428e..c1ca1b55 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -177,3 +177,505 @@ keep_fnames: { } } } + +drop_assign: { + options = { unused: true }; + input: { + function f1() { + var a; + a = 1; + } + function f2() { + var a = 1; + a = 2; + } + function f3(a) { + a = 1; + } + function f4() { + var a; + return a = 1; + } + function f5() { + var a; + return function() { + a = 1; + } + } + } + expect: { + function f1() { + 1; + } + function f2() { + 2; + } + function f3(a) { + 1; + } + function f4() { + return 1; + } + function f5() { + var a; + return function() { + a = 1; + } + } + } +} + +keep_assign: { + options = { unused: "keep_assign" }; + input: { + function f1() { + var a; + a = 1; + } + function f2() { + var a = 1; + a = 2; + } + function f3(a) { + a = 1; + } + function f4() { + var a; + return a = 1; + } + function f5() { + var a; + return function() { + a = 1; + } + } + } + expect: { + function f1() { + var a; + a = 1; + } + function f2() { + var a = 1; + a = 2; + } + function f3(a) { + a = 1; + } + function f4() { + var a; + return a = 1; + } + function f5() { + var a; + return function() { + a = 1; + } + } + } +} + +drop_toplevel_funcs: { + options = { toplevel: "funcs", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, b = 1, c = g; + a = 2; + function g() {} + console.log(b = 3); + } +} + +drop_toplevel_vars: { + options = { toplevel: "vars", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var c = g; + function f(d) { + return function() { + c = 2; + } + } + 2; + function g() {} + function h() {} + console.log(3); + } +} + +drop_toplevel_vars_fargs: { + options = { keep_fargs: false, toplevel: "vars", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var c = g; + function f() { + return function() { + c = 2; + } + } + 2; + function g() {} + function h() {} + console.log(3); + } +} + +drop_toplevel_all: { + options = { toplevel: true, unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + 2; + console.log(3); + } +} + +drop_toplevel_retain: { + options = { top_retain: "f,a,o", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + console.log(3); + } +} + +drop_toplevel_retain_array: { + options = { top_retain: [ "f", "a", "o" ], unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + console.log(3); + } +} + +drop_toplevel_retain_regex: { + options = { top_retain: /^[fao]$/, unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + console.log(3); + } +} + +drop_toplevel_all_retain: { + options = { toplevel: true, top_retain: "f,a,o", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + console.log(3); + } +} + +drop_toplevel_funcs_retain: { + options = { toplevel: "funcs", top_retain: "f,a,o", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + console.log(b = 3); + } +} + +drop_toplevel_vars_retain: { + options = { toplevel: "vars", top_retain: "f,a,o", unused: true }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(3); + } +} + +drop_toplevel_keep_assign: { + options = { toplevel: true, unused: "keep_assign" }; + input: { + var a, b = 1, c = g; + function f(d) { + return function() { + c = 2; + } + } + a = 2; + function g() {} + function h() {} + console.log(b = 3); + } + expect: { + var a, b = 1; + a = 2; + console.log(b = 3); + } +} + +drop_fargs: { + options = { + keep_fargs: false, + unused: true, + } + input: { + function f(a) { + var b = a; + } + } + expect: { + function f() {} + } +} + +drop_fnames: { + options = { + keep_fnames: false, + unused: true, + } + input: { + function f() { + return function g() { + var a = g; + }; + } + } + expect: { + function f() { + return function() {}; + } + } +} + +global_var: { + options = { + side_effects: true, + unused: true, + } + input: { + var a; + function foo(b) { + a; + b; + c; + typeof c === "undefined"; + c + b + a; + b && b.ar(); + return b; + } + } + expect: { + var a; + function foo(b) { + c; + c; + b && b.ar(); + return b; + } + } +} + +iife: { + options = { + side_effects: true, + unused: true, + } + input: { + function f() { + var a; + ~function() {}(b); + } + } + expect: { + function f() { + ~function() {}(b); + } + } +} + +drop_value: { + options = { + side_effects: true, + } + input: { + (1, [2, foo()], 3, {a:1, b:bar()}); + } + expect: { + foo(), bar(); + } +} + +const_assign: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + function f() { + const b = 2; + return 1 + b; + } + + function g() { + const b = 2; + b = 3; + return 1 + b; + } + } + expect: { + function f() { + return 3; + } + + function g() { + const b = 2; + b = 3; + return 1 + b; + } + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 0ff157dc..ae5e58d6 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -598,3 +598,51 @@ unsafe_prototype_function: { var h = "" + ({toString: 0}); } } + +call_args: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + const a = 1; + console.log(a); + +function(a) { + return a; + }(a); + } + expect: { + const a = 1; + console.log(1); + +function(a) { + return a; + }(1); + } +} + +in_boolean_context: { + options = { + booleans: true, + evaluate: true, + } + input: { + !42; + !"foo"; + ![1, 2]; + !/foo/; + !b(42); + !b("foo"); + !b([1, 2]); + !b(/foo/); + } + expect: { + !1; + !1; + !1; + !1; + !b(42); + !b("foo"); + !b([1, 2]); + !b(/foo/); + } +} diff --git a/test/compress/global_defs.js b/test/compress/global_defs.js new file mode 100644 index 00000000..a69d031e --- /dev/null +++ b/test/compress/global_defs.js @@ -0,0 +1,147 @@ +must_replace: { + options = { + global_defs: { + D: "foo bar", + } + } + input: { + console.log(D); + } + expect: { + console.log("foo bar"); + } +} + +keyword: { + options = { + global_defs: { + undefined: 0, + NaN: 1, + Infinity: 2, + }, + } + input: { + console.log(undefined, NaN, Infinity); + } + expect: { + console.log(0, 1, 2); + } +} + +object: { + options = { + evaluate: true, + global_defs: { + CONFIG: { + DEBUG: [ 0 ], + VALUE: 42, + }, + }, + unsafe: true, + } + input: { + function f(CONFIG) { + // CONFIG not global - do not replace + return CONFIG.VALUE; + } + function g() { + var CONFIG = { VALUE: 1 }; + // CONFIG not global - do not replace + return CONFIG.VALUE; + } + function h() { + return CONFIG.VALUE; + } + if (CONFIG.DEBUG[0]) + console.debug("foo"); + } + expect: { + function f(CONFIG) { + return CONFIG.VALUE; + } + function g() { + var CONFIG = { VALUE: 1 }; + return CONFIG.VALUE; + } + function h() { + return 42; + } + if (0) + console.debug("foo"); + } +} + +expanded: { + options = { + global_defs: { + "CONFIG.DEBUG": [ 0 ], + "CONFIG.VALUE": 42, + }, + } + input: { + function f(CONFIG) { + // CONFIG not global - do not replace + return CONFIG.VALUE; + } + function g() { + var CONFIG = { VALUE: 1 }; + // CONFIG not global - do not replace + return CONFIG.VALUE; + } + function h() { + return CONFIG.VALUE; + } + if (CONFIG.DEBUG[0]) + console.debug("foo"); + } + expect: { + function f(CONFIG) { + return CONFIG.VALUE; + } + function g() { + var CONFIG = { VALUE: 1 }; + return CONFIG.VALUE; + } + function h() { + return 42; + } + if ([0][0]) + console.debug("foo"); + } +} + +mixed: { + options = { + evaluate: true, + global_defs: { + "CONFIG.VALUE": 42, + "FOO.BAR": "moo", + }, + properties: true, + } + input: { + const FOO = { BAR: 0 }; + console.log(FOO.BAR); + console.log(++CONFIG.DEBUG); + console.log(++CONFIG.VALUE); + console.log(++CONFIG["VAL" + "UE"]); + console.log(++DEBUG[CONFIG.VALUE]); + CONFIG.VALUE.FOO = "bar"; + console.log(CONFIG); + } + expect: { + const FOO = { BAR: 0 }; + console.log("moo"); + console.log(++CONFIG.DEBUG); + console.log(++CONFIG.VALUE); + console.log(++CONFIG.VALUE); + console.log(++DEBUG[42]); + CONFIG.VALUE.FOO = "bar"; + console.log(CONFIG); + } + expect_warnings: [ + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]', + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', + 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', + ] +} diff --git a/test/compress/hoist_vars.js b/test/compress/hoist_vars.js new file mode 100644 index 00000000..6fe1c773 --- /dev/null +++ b/test/compress/hoist_vars.js @@ -0,0 +1,90 @@ +statements: { + options = { + hoist_funs: false, + hoist_vars: true, + } + input: { + function f() { + var a = 1; + var b = 2; + var c = 3; + function g() {} + return g(a, b, c); + } + } + expect: { + function f() { + var a = 1, b = 2, c = 3; + function g() {} + return g(a, b, c); + } + } +} + +statements_funs: { + options = { + hoist_funs: true, + hoist_vars: true, + } + input: { + function f() { + var a = 1; + var b = 2; + var c = 3; + function g() {} + return g(a, b, c); + } + } + expect: { + function f() { + function g() {} + var a = 1, b = 2, c = 3; + return g(a, b, c); + } + } +} + +sequences: { + options = { + hoist_funs: false, + hoist_vars: true, + } + input: { + function f() { + var a = 1, b = 2; + function g() {} + var c = 3; + return g(a, b, c); + } + } + expect: { + function f() { + var c, a = 1, b = 2; + function g() {} + c = 3; + return g(a, b, c); + } + } +} + +sequences_funs: { + options = { + hoist_funs: true, + hoist_vars: true, + } + input: { + function f() { + var a = 1, b = 2; + function g() {} + var c = 3; + return g(a, b, c); + } + } + expect: { + function f() { + function g() {} + var a = 1, b = 2, c = 3; + return g(a, b, c); + } + } +} diff --git a/test/compress/if_return.js b/test/compress/if_return.js index 78a6e818..0ac45c3c 100644 --- a/test/compress/if_return.js +++ b/test/compress/if_return.js @@ -170,8 +170,51 @@ if_return_7: { } } expect: { - // suboptimal - function f(x){return!!x||(foo(),void bar())} + function f(x){if(x)return!0;foo(),bar()} + } +} + +if_return_8: { + options = { + if_return: true, + sequences: true, + conditionals: true, + side_effects : true, + } + input: { + function f(e) { + if (2 == e) return foo(); + if (3 == e) return bar(); + if (4 == e) return baz(); + fail(e); + } + + function g(e) { + if (a(e)) return foo(); + if (b(e)) return bar(); + if (c(e)) return baz(); + fail(e); + } + + function h(e) { + if (a(e)) return foo(); + else if (b(e)) return bar(); + else if (c(e)) return baz(); + else fail(e); + } + + function i(e) { + if (a(e)) return foo(); + else if (b(e)) return bar(); + else if (c(e)) return baz(); + fail(e); + } + } + expect: { + function f(e){return 2==e?foo():3==e?bar():4==e?baz():void fail(e)} + function g(e){return a(e)?foo():b(e)?bar():c(e)?baz():void fail(e)} + function h(e){return a(e)?foo():b(e)?bar():c(e)?baz():void fail(e)} + function i(e){return a(e)?foo():b(e)?bar():c(e)?baz():void fail(e)} } } @@ -205,3 +248,57 @@ issue_1089: { } } } + +issue_1437: { + options = { + if_return : true, + sequences : true, + conditionals : false + } + input: { + function x() { + if (a()) + return b(); + if (c()) + return d(); + else + e(); + f(); + } + } + expect: { + function x() { + if (a()) + return b(); + if (c()) + return d(); + else + e() + f(); + } + } +} + +issue_1437_conditionals: { + options = { + conditionals : true, + if_return : true, + sequences : true + } + input: { + function x() { + if (a()) + return b(); + if (c()) + return d(); + else + e(); + f(); + } + } + expect: { + function x() { + return a() ? b() : c() ? d() : (e(), f(), void 0); + } + } +} diff --git a/test/compress/issue-1041.js b/test/compress/issue-1041.js index 9dd176fd..cdbc22cc 100644 --- a/test/compress/issue-1041.js +++ b/test/compress/issue-1041.js @@ -13,7 +13,8 @@ const_declaration: { const_pragma: { options = { - evaluate: true + evaluate: true, + reduce_vars: true, }; input: { @@ -27,7 +28,8 @@ const_pragma: { // for completeness' sake not_const: { options = { - evaluate: true + evaluate: true, + reduce_vars: true, }; input: { diff --git a/test/compress/issue-105.js b/test/compress/issue-105.js deleted file mode 100644 index ca17adbf..00000000 --- a/test/compress/issue-105.js +++ /dev/null @@ -1,25 +0,0 @@ -typeof_eq_undefined: { - options = { - comparisons: true - }; - input: { a = typeof b.c != "undefined" } - expect: { a = "undefined" != typeof b.c } -} - -typeof_eq_undefined_unsafe: { - options = { - comparisons: true, - unsafe: true - }; - input: { a = typeof b.c != "undefined" } - expect: { a = void 0 !== b.c } -} - -typeof_eq_undefined_unsafe2: { - options = { - comparisons: true, - unsafe: true - }; - input: { a = "undefined" != typeof b.c } - expect: { a = void 0 !== b.c } -} diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js new file mode 100644 index 00000000..dfbe2100 --- /dev/null +++ b/test/compress/issue-1261.js @@ -0,0 +1,118 @@ +pure_function_calls: { + options = { + evaluate : true, + conditionals : true, + comparisons : true, + side_effects : true, + booleans : true, + unused : true, + if_return : true, + join_vars : true, + cascade : true, + negate_iife : true, + } + input: { + // pure top-level IIFE will be dropped + // @__PURE__ - comment + (function() { + console.log("iife0"); + })(); + + // pure top-level IIFE assigned to unreferenced var will not be dropped + var iife1 = /*@__PURE__*/(function() { + console.log("iife1"); + function iife1() {} + return iife1; + })(); + + (function(){ + // pure IIFE in function scope assigned to unreferenced var will be dropped + var iife2 = /*#__PURE__*/(function() { + console.log("iife2"); + function iife2() {} + return iife2; + })(); + })(); + + // comment #__PURE__ comment + bar(), baz(), quux(); + a.b(), /* @__PURE__ */ c.d.e(), f.g(); + } + expect: { + var iife1 = function() { + console.log("iife1"); + function iife1() {} + return iife1; + }(); + + baz(), quux(); + 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]", + ] +} + +pure_function_calls_toplevel: { + options = { + evaluate : true, + conditionals : true, + comparisons : true, + side_effects : true, + booleans : true, + unused : true, + if_return : true, + join_vars : true, + cascade : true, + negate_iife : true, + toplevel : true, + } + input: { + // pure top-level IIFE will be dropped + // @__PURE__ - comment + (function() { + console.log("iife0"); + })(); + + // pure top-level IIFE assigned to unreferenced var will be dropped + var iife1 = /*@__PURE__*/(function() { + console.log("iife1"); + function iife1() {} + return iife1; + })(); + + (function(){ + // pure IIFE in function scope assigned to unreferenced var will be dropped + var iife2 = /*#__PURE__*/(function() { + console.log("iife2"); + function iife2() {} + return iife2; + })(); + })(); + + // comment #__PURE__ comment + bar(), baz(), quux(); + a.b(), /* @__PURE__ */ c.d.e(), f.g(); + } + expect: { + baz(), quux(); + 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:100,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:101,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:84,33]", + "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:84,12]", + ] +} diff --git a/test/compress/issue-1431.js b/test/compress/issue-1431.js index 731ebba8..9493fd37 100644 --- a/test/compress/issue-1431.js +++ b/test/compress/issue-1431.js @@ -1,3 +1,32 @@ +level_zero: { + options = { + keep_fnames: true + } + mangle = { + keep_fnames: true + } + input: { + function f(x) { + function n(a) { + return a * a; + } + return function() { + return x; + }; + } + } + expect: { + function f(r) { + function n(n) { + return n * n; + } + return function() { + return r; + }; + } + } +} + level_one: { options = { keep_fnames: true diff --git a/test/compress/issue-1443.js b/test/compress/issue-1443.js new file mode 100644 index 00000000..a2565872 --- /dev/null +++ b/test/compress/issue-1443.js @@ -0,0 +1,69 @@ +// tests assume that variable `undefined` not redefined and has `void 0` as value + +unsafe_undefined: { + options = { + if_return: true, + unsafe: true + } + mangle = {} + input: { + function f(undefined) { + return function() { + if (a) + return b; + if (c) + return d; + }; + } + } + expect: { + function f(n) { + return function() { + if (a) + return b; + if (c) + return d; + else + return n; + }; + } + } +} + +keep_fnames: { + options = { + if_return: true, + unsafe: true + } + mangle = { + keep_fnames: true + } + input: { + function f(undefined) { + return function() { + function n(a) { + return a * a; + } + if (a) + return b; + if (c) + return d; + }; + } + } + expect: { + function f(r) { + return function() { + function n(n) { + return n * n; + } + if (a) + return b; + if (c) + return d; + else + return r; + }; + } + } +} diff --git a/test/compress/issue-1446.js b/test/compress/issue-1446.js new file mode 100644 index 00000000..3d69aa09 --- /dev/null +++ b/test/compress/issue-1446.js @@ -0,0 +1,71 @@ +typeof_eq_undefined: { + options = { + comparisons: true + } + input: { + var a = typeof b != "undefined"; + b = typeof a != "undefined"; + var c = typeof d.e !== "undefined"; + var f = "undefined" === typeof g; + g = "undefined" === typeof f; + var h = "undefined" == typeof i.j; + } + expect: { + var a = "undefined" != typeof b; + b = void 0 !== a; + var c = void 0 !== d.e; + var f = "undefined" == typeof g; + g = void 0 === f; + var h = void 0 === i.j; + } +} + +typeof_eq_undefined_ie8: { + options = { + comparisons: true, + screw_ie8: false + } + input: { + var a = typeof b != "undefined"; + b = typeof a != "undefined"; + var c = typeof d.e !== "undefined"; + var f = "undefined" === typeof g; + g = "undefined" === typeof f; + var h = "undefined" == typeof i.j; + } + expect: { + var a = "undefined" != typeof b; + b = void 0 !== a; + var c = "undefined" != typeof d.e; + var f = "undefined" == typeof g; + g = void 0 === f; + var h = "undefined" == typeof i.j; + } +} + +undefined_redefined: { + options = { + comparisons: true + } + input: { + function f(undefined) { + var n = 1; + return typeof n == "undefined"; + } + } + expect_exact: "function f(undefined){var n=1;return void 0===n}" +} + +undefined_redefined_mangle: { + options = { + comparisons: true + } + mangle = {} + input: { + function f(undefined) { + var n = 1; + return typeof n == "undefined"; + } + } + expect_exact: "function f(n){var r=1;return void 0===r}" +} diff --git a/test/compress/issue-1447.js b/test/compress/issue-1447.js new file mode 100644 index 00000000..163acbc2 --- /dev/null +++ b/test/compress/issue-1447.js @@ -0,0 +1,45 @@ +else_with_empty_block: { + options = {} + input: { + if (x) + yes(); + else { + } + } + expect_exact: "if(x)yes();" +} + +else_with_empty_statement: { + options = {} + input: { + if (x) + yes(); + else + ; + } + expect_exact: "if(x)yes();" +} + +conditional_false_stray_else_in_loop: { + options = { + evaluate : true, + comparisons : true, + booleans : true, + unused : true, + loops : true, + side_effects : true, + dead_code : true, + hoist_vars : true, + join_vars : true, + if_return : true, + cascade : true, + conditionals : false, + } + input: { + for (var i = 1; i <= 4; ++i) { + if (i <= 2) continue; + console.log(i); + } + } + expect_exact: "for(var i=1;i<=4;++i)if(!(i<=2))console.log(i);" +} diff --git a/test/compress/issue-208.js b/test/compress/issue-208.js index 2f103786..fb9861f6 100644 --- a/test/compress/issue-208.js +++ b/test/compress/issue-208.js @@ -27,3 +27,44 @@ do_update_rhs: { MY_DEBUG += 0; } } + +mixed: { + options = { + evaluate: true, + global_defs: { + DEBUG: 0, + ENV: 1, + FOO: 2, + } + } + input: { + const ENV = 3; + var FOO = 4; + f(ENV * 10); + --FOO; + DEBUG = 1; + DEBUG++; + DEBUG += 1; + f(DEBUG); + x = DEBUG; + } + expect: { + const ENV = 3; + var FOO = 4; + f(10); + --FOO; + DEBUG = 1; + DEBUG++; + DEBUG += 1; + f(0); + x = 0; + } + expect_warnings: [ + 'WARN: global_defs ENV redefined [test/compress/issue-208.js:41,14]', + 'WARN: global_defs FOO redefined [test/compress/issue-208.js:42,12]', + 'WARN: global_defs FOO redefined [test/compress/issue-208.js:44,10]', + 'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:45,8]', + 'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:46,8]', + 'WARN: global_defs DEBUG redefined [test/compress/issue-208.js:47,8]', + ] +} diff --git a/test/compress/issue-979.js b/test/compress/issue-979.js index bae15db8..7ed5801d 100644 --- a/test/compress/issue-979.js +++ b/test/compress/issue-979.js @@ -82,7 +82,7 @@ issue979_test_negated_is_best: { 1!=a||2!=b||foo(); } function f7() { - return 1!=a&&2!=b?bar():void foo(); + if(1!=a&&2!=b)return bar();foo() } } } diff --git a/test/compress/loops.js b/test/compress/loops.js index 78f618aa..ca05461c 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -187,3 +187,32 @@ keep_collapse_const_in_own_block_scope_2: { console.log(c); } } + +evaluate: { + options = { + loops: true, + dead_code: true, + evaluate: true, + }; + input: { + while (true) { + a(); + } + while (false) { + b(); + } + do { + c(); + } while (true); + do { + d(); + } while (false); + } + expect: { + for(;;) + a(); + for(;;) + c(); + d(); + } +} diff --git a/test/compress/max_line_len.js b/test/compress/max_line_len.js new file mode 100644 index 00000000..b9e09178 --- /dev/null +++ b/test/compress/max_line_len.js @@ -0,0 +1,28 @@ +too_short: { + beautify = { + max_line_len: 10, + } + input: { + function f(a) { + return { c: 42, d: a(), e: "foo"}; + } + } + expect_exact: 'function f(a){\nreturn{\nc:42,\nd:a(),\ne:"foo"}}' + expect_warnings: [ + "WARN: Output exceeds 10 characters" + ] +} + +just_enough: { + beautify = { + max_line_len: 14, + } + input: { + function f(a) { + return { c: 42, d: a(), e: "foo"}; + } + } + expect_exact: 'function f(a){\nreturn{c:42,\nd:a(),e:"foo"}\n}' + expect_warnings: [ + ] +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 0c111604..001795c5 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -10,6 +10,16 @@ negate_iife_1: { } } +negate_iife_1_off: { + options = { + negate_iife: false, + }; + input: { + (function(){ stuff() })(); + } + expect_exact: '(function(){stuff()})();' +} + negate_iife_2: { options = { negate_iife: true @@ -25,6 +35,7 @@ negate_iife_2: { negate_iife_3: { options = { negate_iife: true, + conditionals: true }; input: { (function(){ return true })() ? console.log(true) : console.log(false); @@ -34,9 +45,23 @@ negate_iife_3: { } } -negate_iife_3: { +negate_iife_3_off: { + options = { + negate_iife: false, + conditionals: true, + }; + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + !function(){ return true }() ? console.log(false) : console.log(true); + } +} + +negate_iife_4: { options = { negate_iife: true, + conditionals: true, sequences: true }; input: { @@ -52,7 +77,42 @@ negate_iife_3: { } } -negate_iife_4: { +sequence_off: { + options = { + negate_iife: false, + conditionals: true, + sequences: true, + passes: 2, + }; + input: { + function f() { + (function(){ return true })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + function g() { + (function(){ + console.log("something"); + })(); + (function(){ return true })() ? console.log(true) : console.log(false); + } + } + expect: { + function f() { + !function(){ return true }() ? console.log(false) : console.log(true), function(){ + console.log("something"); + }(); + } + function g() { + (function(){ + console.log("something"); + })(), function(){ return true }() ? console.log(true) : console.log(false); + } + } +} + +negate_iife_5: { options = { negate_iife: true, sequences: true, @@ -75,6 +135,29 @@ negate_iife_4: { } } +negate_iife_5_off: { + options = { + negate_iife: false, + sequences: true, + conditionals: true, + }; + input: { + if ((function(){ return true })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + !function(){ return true }() ? bar(false) : foo(true), function(){ + console.log("something"); + }(); + } +} + negate_iife_nested: { options = { negate_iife: true, @@ -107,6 +190,38 @@ negate_iife_nested: { } } +negate_iife_nested_off: { + options = { + negate_iife: false, + sequences: true, + conditionals: true, + }; + input: { + function Foo(f) { + this.f = f; + } + new Foo(function() { + (function(x) { + (function(y) { + console.log(y); + })(x); + })(7); + }).f(); + } + expect: { + function Foo(f) { + this.f = f; + } + new Foo(function() { + (function(x) { + (function(y) { + console.log(y); + })(x); + })(7); + }).f(); + } +} + negate_iife_issue_1073: { options = { negate_iife: true, @@ -172,3 +287,36 @@ issue_1254_negate_iife_nested: { } expect_exact: '!function(){return function(){console.log("test")}}()()()()();' } + +issue_1288: { + options = { + negate_iife: true, + conditionals: true, + }; + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w || function f() {}(); + x || function() { + x = {}; + }(); + y ? function() {}() : function(z) { + return z; + }(0); + } +} diff --git a/test/compress/properties.js b/test/compress/properties.js index 7ad54ebe..29bdfe2a 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -539,3 +539,19 @@ first_256_hex_chars_as_properties: { }; } } + +native_prototype: { + options = { + unsafe_proto: true, + } + input: { + Array.prototype.splice.apply(a, [1, 2, b, c]); + Object.prototype.hasOwnProperty.call(d, "foo"); + String.prototype.indexOf.call(e, "bar"); + } + expect: { + [].splice.apply(a, [1, 2, b, c]); + ({}).hasOwnProperty.call(d, "foo"); + "".indexOf.call(e, "bar"); + } +} diff --git a/test/compress/pure_funcs.js b/test/compress/pure_funcs.js new file mode 100644 index 00000000..3cc529a8 --- /dev/null +++ b/test/compress/pure_funcs.js @@ -0,0 +1,295 @@ +array: { + options = { + pure_funcs: [ "Math.floor" ], + side_effects: true, + } + input: { + var a; + function f(b) { + Math.floor(a / b); + Math.floor(c / b); + } + } + expect: { + var a; + function f(b) { + c; + } + } +} + +func: { + options = { + pure_funcs: function(node) { + return !~node.args[0].print_to_string().indexOf("a"); + }, + side_effects: true, + } + input: { + function f(a, b) { + Math.floor(a / b); + Math.floor(c / b); + } + } + expect: { + function f(a, b) { + Math.floor(c / b); + } + } +} + +side_effects: { + options = { + pure_funcs: [ "console.log" ], + side_effects: true, + } + input: { + function f(a, b) { + console.log(a()); + console.log(b); + } + } + expect: { + function f(a, b) { + a(); + } + } +} + +unused: { + options = { + pure_funcs: [ "pure" ], + side_effects: true, + unused: true, + } + input: { + function foo() { + var u = pure(1); + var x = pure(2); + var y = pure(x); + var z = pure(pure(side_effects())); + return pure(3); + } + } + expect: { + function foo() { + side_effects(); + return pure(3); + } + } +} + +babel: { + options = { + pure_funcs: [ "_classCallCheck" ], + side_effects: true, + unused: true, + } + input: { + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) + throw new TypeError("Cannot call a class as a function"); + } + var Foo = function Foo() { + _classCallCheck(this, Foo); + }; + } + expect: { + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) + throw new TypeError("Cannot call a class as a function"); + } + var Foo = function() { + }; + } +} + +conditional: { + options = { + pure_funcs: [ "pure" ], + side_effects: true, + } + input: { + pure(1 | a() ? 2 & b() : 7 ^ c()); + pure(1 | a() ? 2 & b() : 5); + pure(1 | a() ? 4 : 7 ^ c()); + pure(1 | a() ? 4 : 5); + pure(3 ? 2 & b() : 7 ^ c()); + pure(3 ? 2 & b() : 5); + pure(3 ? 4 : 7 ^ c()); + pure(3 ? 4 : 5); + } + expect: { + 1 | a() ? b() : c(); + 1 | a() && b(); + 1 | a() || c(); + a(); + 3 ? b() : c(); + 3 && b(); + 3 || c(); + } +} + +relational: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + foo() in foo(); + foo() instanceof bar(); + foo() < "bar"; + bar() > foo(); + bar() != bar(); + bar() !== "bar"; + "bar" == foo(); + "bar" === bar(); + "bar" >= "bar"; + } + expect: { + bar(); + bar(); + bar(), bar(); + bar(); + bar(); + } +} + +arithmetic: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + foo() + foo(); + foo() - bar(); + foo() * "bar"; + bar() / foo(); + bar() & bar(); + bar() | "bar"; + "bar" >> foo(); + "bar" << bar(); + "bar" >>> "bar"; + } + expect: { + bar(); + bar(); + bar(), bar(); + bar(); + bar(); + } +} + +boolean_and: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + foo() && foo(); + foo() && bar(); + foo() && "bar"; + bar() && foo(); + bar() && bar(); + bar() && "bar"; + "bar" && foo(); + "bar" && bar(); + "bar" && "bar"; + } + expect: { + foo() && bar(); + bar(); + bar() && bar(); + bar(); + "bar" && bar(); + } +} + +boolean_or: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + foo() || foo(); + foo() || bar(); + foo() || "bar"; + bar() || foo(); + bar() || bar(); + bar() || "bar"; + "bar" || foo(); + "bar" || bar(); + "bar" || "bar"; + } + expect: { + foo() || bar(); + bar(); + bar() || bar(); + bar(); + "bar" || bar(); + } +} + +assign: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + var a; + function f(b) { + a = foo(); + b *= 4 + foo(); + c >>= 0 | foo(); + } + } + expect: { + var a; + function f(b) { + a = foo(); + b *= 4 + foo(); + c >>= 0 | foo(); + } + } +} + +unary: { + options = { + pure_funcs: [ "foo" ], + side_effects :true, + } + input: { + typeof foo(); + typeof bar(); + typeof "bar"; + void foo(); + void bar(); + void "bar"; + delete a[foo()]; + delete a[bar()]; + delete a["bar"]; + a[foo()]++; + a[bar()]++; + a["bar"]++; + --a[foo()]; + --a[bar()]; + --a["bar"]; + ~foo(); + ~bar(); + ~"bar"; + } + expect: { + bar(); + bar(); + delete a[foo()]; + delete a[bar()]; + delete a["bar"]; + a[foo()]++; + a[bar()]++; + a["bar"]++; + --a[foo()]; + --a[bar()]; + --a["bar"]; + bar(); + } +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 2301a92a..d9d02efa 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -108,8 +108,6 @@ modified: { } console.log(a + b); console.log(b + c); - // TODO: as "modified" is determined in "figure_out_scope", - // even "passes" wouldn't improve this any further console.log(a + c); console.log(a + b + c); } @@ -350,3 +348,125 @@ unsafe_evaluate_equality: { } } } + +passes: { + options = { + conditionals: true, + evaluate: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + function f() { + var a = 1, b = 2, c = 3; + if (a) { + b = c; + } else { + c = b; + } + console.log(a + b); + console.log(b + c); + console.log(a + c); + console.log(a + b + c); + } + } + expect: { + function f() { + var b = 2, c = 3; + b = c; + console.log(1 + b); + console.log(b + 3); + console.log(4); + console.log(1 + b + 3); + } + } +} + +iife: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + !function(a, b, c) { + b++; + console.log(a - 1, b * 1, c + 2); + }(1, 2, 3); + } + expect: { + !function(a, b, c) { + b++; + console.log(0, 1 * b, 5); + }(1, 2, 3); + } +} + +iife_new: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + var A = new function(a, b, c) { + b++; + console.log(a - 1, b * 1, c + 2); + }(1, 2, 3); + } + expect: { + var A = new function(a, b, c) { + b++; + console.log(0, 1 * b, 5); + }(1, 2, 3); + } +} + +multi_def: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f(a) { + if (a) + var b = 1; + else + var b = 2 + console.log(b + 1); + } + } + expect: { + function f(a) { + if (a) + var b = 1; + else + var b = 2 + console.log(b + 1); + } + } +} + +multi_def_2: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + if (code == 16) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (code == 17) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (code == 18) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } + expect: { + if (16 == code) + var bitsLength = 2, bitsOffset = 3, what = len; + else if (17 == code) + var bitsLength = 3, bitsOffset = 3, what = (len = 0); + else if (18 == code) + var bitsLength = 7, bitsOffset = 11, what = (len = 0); + var repeatLength = this.getBits(bitsLength) + bitsOffset; + } +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 0e3319ab..d93f5237 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -169,3 +169,85 @@ for_sequences: { for (y = 5; false;); } } + +limit_1: { + options = { + sequences: 3, + }; + input: { + a; + b; + c; + d; + e; + f; + g; + h; + i; + j; + k; + } + expect: { + a, b, c; + d, e, f; + g, h, i; + j, k; + } +} + +limit_2: { + options = { + sequences: 3, + }; + input: { + a, b; + c, d; + e, f; + g, h; + i, j; + k; + } + expect: { + a, b, c, d; + e, f, g, h; + i, j, k; + } +} + +negate_iife_for: { + options = { + sequences: true, + negate_iife: true, + }; + input: { + (function() {})(); + for (i = 0; i < 5; i++) console.log(i); + + (function() {})(); + for (; i < 5; i++) console.log(i); + } + expect: { + for (!function() {}(), i = 0; i < 5; i++) console.log(i); + for (function() {}(); i < 5; i++) console.log(i); + } +} + +iife: { + options = { + sequences: true, + }; + input: { + x = 42; + (function a() {})(); + !function b() {}(); + ~function c() {}(); + +function d() {}(); + -function e() {}(); + void function f() {}(); + typeof function g() {}(); + } + expect: { + x = 42, function a() {}(), function b() {}(), function c() {}(), + function d() {}(), function e() {}(), function f() {}(), function g() {}() + } +} diff --git a/test/input/global_defs/nested.js b/test/input/global_defs/nested.js new file mode 100644 index 00000000..dbf57909 --- /dev/null +++ b/test/input/global_defs/nested.js @@ -0,0 +1 @@ +console.log(C.V, C.D); diff --git a/test/input/global_defs/simple.js b/test/input/global_defs/simple.js new file mode 100644 index 00000000..44d515e3 --- /dev/null +++ b/test/input/global_defs/simple.js @@ -0,0 +1 @@ +console.log(D); diff --git a/test/input/issue-1482/bracketize.js b/test/input/issue-1482/bracketize.js new file mode 100644 index 00000000..2c2b103c --- /dev/null +++ b/test/input/issue-1482/bracketize.js @@ -0,0 +1,73 @@ +if (x) { + foo(); +} + +if (x) { + foo(); +} else { + baz(); +} + +if (x) { + foo(); +} else if (y) { + bar(); +} else { + baz(); +} + +if (x) { + if (y) { + foo(); + } else { + bar(); + } +} else { + baz(); +} + +if (x) { + foo(); +} else if (y) { + bar(); +} else if (z) { + baz(); +} else { + moo(); +} + +function f() { + if (x) { + foo(); + } + if (x) { + foo(); + } else { + baz(); + } + if (x) { + foo(); + } else if (y) { + bar(); + } else { + baz(); + } + if (x) { + if (y) { + foo(); + } else { + bar(); + } + } else { + baz(); + } + if (x) { + foo(); + } else if (y) { + bar(); + } else if (z) { + baz(); + } else { + moo(); + } +} diff --git a/test/input/issue-1482/default.js b/test/input/issue-1482/default.js new file mode 100644 index 00000000..14054e98 --- /dev/null +++ b/test/input/issue-1482/default.js @@ -0,0 +1,17 @@ +if (x) foo(); + +if (x) foo(); else baz(); + +if (x) foo(); else if (y) bar(); else baz(); + +if (x) if (y) foo(); else bar(); else baz(); + +if (x) foo(); else if (y) bar(); else if (z) baz(); else moo(); + +function f() { + if (x) foo(); + if (x) foo(); else baz(); + if (x) foo(); else if (y) bar(); else baz(); + if (x) if (y) foo(); else bar(); else baz(); + if (x) foo(); else if (y) bar(); else if (z) baz(); else moo(); +} diff --git a/test/input/issue-1482/input.js b/test/input/issue-1482/input.js new file mode 100644 index 00000000..0186e82c --- /dev/null +++ b/test/input/issue-1482/input.js @@ -0,0 +1,12 @@ +if (x) foo(); +if (x) foo(); else baz(); +if (x) foo(); else if (y) bar(); else baz(); +if (x) if (y) foo(); else bar(); else baz(); +if (x) foo(); else if (y) bar(); else if (z) baz(); else moo(); +function f() { +if (x) foo(); +if (x) foo(); else baz(); +if (x) foo(); else if (y) bar(); else baz(); +if (x) if (y) foo(); else bar(); else baz(); +if (x) foo(); else if (y) bar(); else if (z) baz(); else moo(); +} diff --git a/test/jetstream.js b/test/jetstream.js new file mode 100644 index 00000000..a8195389 --- /dev/null +++ b/test/jetstream.js @@ -0,0 +1,87 @@ +#! /usr/bin/env node +// -*- js -*- + +"use strict"; + +var site = "http://browserbench.org/JetStream/"; +if (typeof phantom == "undefined") { + // workaround for tty output truncation upon process.exit() + [process.stdout, process.stderr].forEach(function(stream){ + if (stream._handle && stream._handle.setBlocking) + stream._handle.setBlocking(true); + }); + var args = process.argv.slice(2); + if (!args.length) { + args.push("-mc", "warnings=false"); + } + args.push("--stats"); + var child_process = require("child_process"); + try { + require("phantomjs-prebuilt"); + } catch(e) { + child_process.execSync("npm install phantomjs-prebuilt@2.1.14"); + } + var http = require("http"); + var server = http.createServer(function(request, response) { + request.resume(); + var url = decodeURIComponent(request.url.slice(1)); + var stderr = ""; + var uglifyjs = child_process.fork("bin/uglifyjs", args, { + silent: true + }).on("exit", function(code) { + console.log("uglifyjs", url.indexOf(site) == 0 ? url.slice(site.length) : url, args.join(" ")); + console.log(stderr); + if (code) throw new Error("uglifyjs failed with code " + code); + }); + uglifyjs.stderr.on("data", function(data) { + stderr += data; + }).setEncoding("utf8"); + uglifyjs.stdout.pipe(response); + http.get(url, function(res) { + res.pipe(uglifyjs.stdin); + }); + }).listen().on("listening", function() { + var phantomjs = require("phantomjs-prebuilt"); + var program = phantomjs.exec(process.argv[1], server.address().port); + program.stdout.pipe(process.stdout); + program.stderr.pipe(process.stderr); + program.on("exit", function(code) { + server.close(); + if (code) throw new Error("JetStream failed!"); + console.log("JetStream completed successfully."); + }); + }); + server.timeout = 0; +} else { + var page = require("webpage").create(); + page.onError = function(msg, trace) { + var body = [ msg ]; + if (trace) trace.forEach(function(t) { + body.push(" " + (t.function || "Anonymous function") + " (" + t.file + ":" + t.line + ")"); + }); + console.error(body.join("\n")); + phantom.exit(1); + }; + var url = "http://localhost:" + require("system").args[1] + "/"; + page.onResourceRequested = function(requestData, networkRequest) { + if (/\.js$/.test(requestData.url)) + networkRequest.changeUrl(url + encodeURIComponent(requestData.url)); + } + page.onConsoleMessage = function(msg) { + if (/Error:/i.test(msg)) { + console.error(msg); + phantom.exit(1); + } + console.log(msg); + if (~msg.indexOf("Raw results:")) { + phantom.exit(); + } + }; + page.open(site, function(status) { + if (status != "success") phantomjs.exit(1); + page.evaluate(function() { + JetStream.switchToQuick(); + JetStream.start(); + }); + }); +} diff --git a/test/mocha/accessorTokens-1492.js b/test/mocha/accessorTokens-1492.js new file mode 100644 index 00000000..861414ee --- /dev/null +++ b/test/mocha/accessorTokens-1492.js @@ -0,0 +1,32 @@ +var UglifyJS = require('../../'); +var assert = require("assert"); + +describe("Accessor tokens", function() { + it("Should fill the token information for accessors (issue #1492)", function() { + // location 0 1 2 3 4 + // 01234567890123456789012345678901234567890123456789 + var ast = UglifyJS.parse("var obj = { get latest() { return undefined; } }"); + + // test all AST_ObjectProperty tokens are set as expected + var checkedAST_ObjectProperty = false; + var checkWalker = new UglifyJS.TreeWalker(function(node, descend) { + if (node instanceof UglifyJS.AST_ObjectProperty) { + checkedAST_ObjectProperty = true; + + assert.equal(node.start.pos, 12); + assert.equal(node.end.endpos, 46); + + assert(node.key instanceof UglifyJS.AST_SymbolRef); + assert.equal(node.key.start.pos, 16); + assert.equal(node.key.end.endpos, 22); + + assert(node.value instanceof UglifyJS.AST_Accessor); + assert.equal(node.value.start.pos, 22); + assert.equal(node.value.end.endpos, 46); + + } + }); + ast.walk(checkWalker); + assert(checkedAST_ObjectProperty, "AST_ObjectProperty not found"); + }); +});
\ No newline at end of file diff --git a/test/mocha/cli.js b/test/mocha/cli.js index a8de05c5..450df1fd 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -1,10 +1,11 @@ var assert = require("assert"); var exec = require("child_process").exec; +var readFileSync = require("fs").readFileSync; describe("bin/uglifyjs", function () { var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs'; it("should produce a functional build when using --self", function (done) { - this.timeout(5000); + this.timeout(15000); var command = uglifyjscmd + ' --self -cm --wrap WrappedUglifyJS'; @@ -100,4 +101,54 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should work with --define (simple)", function (done) { + var command = uglifyjscmd + ' test/input/global_defs/simple.js --define D=5 -c'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "console.log(5);\n"); + done(); + }); + }); + it("Should work with --define (nested)", function (done) { + var command = uglifyjscmd + ' test/input/global_defs/nested.js --define C.D=5,C.V=3 -c'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "console.log(3,5);\n"); + done(); + }); + }); + it("Should work with --define (AST_Node)", function (done) { + var command = uglifyjscmd + ' test/input/global_defs/simple.js --define console.log=stdout.println -c'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, "stdout.println(D);\n"); + done(); + }); + }); + it("Should work with `--beautify`", function (done) { + var command = uglifyjscmd + ' test/input/issue-1482/input.js -b'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, readFileSync("test/input/issue-1482/default.js", "utf8")); + done(); + }); + }); + it("Should work with `--beautify bracketize`", function (done) { + var command = uglifyjscmd + ' test/input/issue-1482/input.js -b bracketize'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, readFileSync("test/input/issue-1482/bracketize.js", "utf8")); + done(); + }); + }); }); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 70cf73ae..8fe1565f 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -95,4 +95,19 @@ describe("minify", function() { assert.strictEqual(code, "var a=function(n){return n};"); }); }); + + describe("#__PURE__", function() { + it("should drop #__PURE__ hint after use", function() { + var result = Uglify.minify('//@__PURE__ comment1 #__PURE__ comment2\n foo(), bar();', { + fromString: true, + output: { + comments: "all", + beautify: false, + } + }); + var code = result.code; + assert.strictEqual(code, "// comment1 comment2\nbar();"); + }); + }); + }); diff --git a/test/mocha/operator.js b/test/mocha/operator.js new file mode 100644 index 00000000..adef3abd --- /dev/null +++ b/test/mocha/operator.js @@ -0,0 +1,489 @@ +var UglifyJS = require("../../"); +var assert = require("assert"); + +describe("operator", function() { + it("Should handle mixing of ++/+/--/- correctly", function() { + function evaluate(exp) { + return new Function("var a=1,b=2,c=" + exp + ";return{a:a,b:b,c:c}")(); + } + + [ "", "+", "-" ].forEach(function(p) { + [ "++a", "--a", "a", "a--", "a++" ].forEach(function(a) { + [ "+", "-" ].forEach(function(o) { + [ "", "+", "-" ].forEach(function(q) { + [ "++b", "--b", "b", "b--", "b++" ].forEach(function(b) { + var exp = [p, a, o, q, b].join(" "); + var orig = evaluate(exp); + var uglify = evaluate(UglifyJS.parse(exp).print_to_string()); + assert.strictEqual(orig.a, uglify.a); + assert.strictEqual(orig.b, uglify.b); + assert.strictEqual(orig.c, uglify.c); + var beautify = evaluate(UglifyJS.parse(exp).print_to_string({ + beautify: true + })); + assert.strictEqual(orig.a, beautify.a); + assert.strictEqual(orig.b, beautify.b); + assert.strictEqual(orig.c, beautify.c); + }); + }); + }); + }); + }); + }); + it("Should remove extraneous spaces", function() { + [ + [ "++a + ++b", "++a+ ++b" ], + [ "++a + --b", "++a+--b" ], + [ "++a + b", "++a+b" ], + [ "++a + b--", "++a+b--" ], + [ "++a + b++", "++a+b++" ], + [ "++a + + ++b", "++a+ + ++b" ], + [ "++a + + --b", "++a+ +--b" ], + [ "++a + + b", "++a+ +b" ], + [ "++a + + b--", "++a+ +b--" ], + [ "++a + + b++", "++a+ +b++" ], + [ "++a + - ++b", "++a+-++b" ], + [ "++a + - --b", "++a+- --b" ], + [ "++a + - b", "++a+-b" ], + [ "++a + - b--", "++a+-b--" ], + [ "++a + - b++", "++a+-b++" ], + [ "++a - ++b", "++a-++b" ], + [ "++a - --b", "++a- --b" ], + [ "++a - b", "++a-b" ], + [ "++a - b--", "++a-b--" ], + [ "++a - b++", "++a-b++" ], + [ "++a - + ++b", "++a-+ ++b" ], + [ "++a - + --b", "++a-+--b" ], + [ "++a - + b", "++a-+b" ], + [ "++a - + b--", "++a-+b--" ], + [ "++a - + b++", "++a-+b++" ], + [ "++a - - ++b", "++a- -++b" ], + [ "++a - - --b", "++a- - --b" ], + [ "++a - - b", "++a- -b" ], + [ "++a - - b--", "++a- -b--" ], + [ "++a - - b++", "++a- -b++" ], + [ "--a + ++b", "--a+ ++b" ], + [ "--a + --b", "--a+--b" ], + [ "--a + b", "--a+b" ], + [ "--a + b--", "--a+b--" ], + [ "--a + b++", "--a+b++" ], + [ "--a + + ++b", "--a+ + ++b" ], + [ "--a + + --b", "--a+ +--b" ], + [ "--a + + b", "--a+ +b" ], + [ "--a + + b--", "--a+ +b--" ], + [ "--a + + b++", "--a+ +b++" ], + [ "--a + - ++b", "--a+-++b" ], + [ "--a + - --b", "--a+- --b" ], + [ "--a + - b", "--a+-b" ], + [ "--a + - b--", "--a+-b--" ], + [ "--a + - b++", "--a+-b++" ], + [ "--a - ++b", "--a-++b" ], + [ "--a - --b", "--a- --b" ], + [ "--a - b", "--a-b" ], + [ "--a - b--", "--a-b--" ], + [ "--a - b++", "--a-b++" ], + [ "--a - + ++b", "--a-+ ++b" ], + [ "--a - + --b", "--a-+--b" ], + [ "--a - + b", "--a-+b" ], + [ "--a - + b--", "--a-+b--" ], + [ "--a - + b++", "--a-+b++" ], + [ "--a - - ++b", "--a- -++b" ], + [ "--a - - --b", "--a- - --b" ], + [ "--a - - b", "--a- -b" ], + [ "--a - - b--", "--a- -b--" ], + [ "--a - - b++", "--a- -b++" ], + [ "a + ++b", "a+ ++b" ], + [ "a + --b", "a+--b" ], + [ "a + b", "a+b" ], + [ "a + b--", "a+b--" ], + [ "a + b++", "a+b++" ], + [ "a + + ++b", "a+ + ++b" ], + [ "a + + --b", "a+ +--b" ], + [ "a + + b", "a+ +b" ], + [ "a + + b--", "a+ +b--" ], + [ "a + + b++", "a+ +b++" ], + [ "a + - ++b", "a+-++b" ], + [ "a + - --b", "a+- --b" ], + [ "a + - b", "a+-b" ], + [ "a + - b--", "a+-b--" ], + [ "a + - b++", "a+-b++" ], + [ "a - ++b", "a-++b" ], + [ "a - --b", "a- --b" ], + [ "a - b", "a-b" ], + [ "a - b--", "a-b--" ], + [ "a - b++", "a-b++" ], + [ "a - + ++b", "a-+ ++b" ], + [ "a - + --b", "a-+--b" ], + [ "a - + b", "a-+b" ], + [ "a - + b--", "a-+b--" ], + [ "a - + b++", "a-+b++" ], + [ "a - - ++b", "a- -++b" ], + [ "a - - --b", "a- - --b" ], + [ "a - - b", "a- -b" ], + [ "a - - b--", "a- -b--" ], + [ "a - - b++", "a- -b++" ], + [ "a-- + ++b", "a--+ ++b" ], + [ "a-- + --b", "a--+--b" ], + [ "a-- + b", "a--+b" ], + [ "a-- + b--", "a--+b--" ], + [ "a-- + b++", "a--+b++" ], + [ "a-- + + ++b", "a--+ + ++b" ], + [ "a-- + + --b", "a--+ +--b" ], + [ "a-- + + b", "a--+ +b" ], + [ "a-- + + b--", "a--+ +b--" ], + [ "a-- + + b++", "a--+ +b++" ], + [ "a-- + - ++b", "a--+-++b" ], + [ "a-- + - --b", "a--+- --b" ], + [ "a-- + - b", "a--+-b" ], + [ "a-- + - b--", "a--+-b--" ], + [ "a-- + - b++", "a--+-b++" ], + [ "a-- - ++b", "a---++b" ], + [ "a-- - --b", "a--- --b" ], + [ "a-- - b", "a---b" ], + [ "a-- - b--", "a---b--" ], + [ "a-- - b++", "a---b++" ], + [ "a-- - + ++b", "a---+ ++b" ], + [ "a-- - + --b", "a---+--b" ], + [ "a-- - + b", "a---+b" ], + [ "a-- - + b--", "a---+b--" ], + [ "a-- - + b++", "a---+b++" ], + [ "a-- - - ++b", "a--- -++b" ], + [ "a-- - - --b", "a--- - --b" ], + [ "a-- - - b", "a--- -b" ], + [ "a-- - - b--", "a--- -b--" ], + [ "a-- - - b++", "a--- -b++" ], + [ "a++ + ++b", "a+++ ++b" ], + [ "a++ + --b", "a+++--b" ], + [ "a++ + b", "a+++b" ], + [ "a++ + b--", "a+++b--" ], + [ "a++ + b++", "a+++b++" ], + [ "a++ + + ++b", "a+++ + ++b" ], + [ "a++ + + --b", "a+++ +--b" ], + [ "a++ + + b", "a+++ +b" ], + [ "a++ + + b--", "a+++ +b--" ], + [ "a++ + + b++", "a+++ +b++" ], + [ "a++ + - ++b", "a+++-++b" ], + [ "a++ + - --b", "a+++- --b" ], + [ "a++ + - b", "a+++-b" ], + [ "a++ + - b--", "a+++-b--" ], + [ "a++ + - b++", "a+++-b++" ], + [ "a++ - ++b", "a++-++b" ], + [ "a++ - --b", "a++- --b" ], + [ "a++ - b", "a++-b" ], + [ "a++ - b--", "a++-b--" ], + [ "a++ - b++", "a++-b++" ], + [ "a++ - + ++b", "a++-+ ++b" ], + [ "a++ - + --b", "a++-+--b" ], + [ "a++ - + b", "a++-+b" ], + [ "a++ - + b--", "a++-+b--" ], + [ "a++ - + b++", "a++-+b++" ], + [ "a++ - - ++b", "a++- -++b" ], + [ "a++ - - --b", "a++- - --b" ], + [ "a++ - - b", "a++- -b" ], + [ "a++ - - b--", "a++- -b--" ], + [ "a++ - - b++", "a++- -b++" ], + [ "+ ++a + ++b", "+ ++a+ ++b" ], + [ "+ ++a + --b", "+ ++a+--b" ], + [ "+ ++a + b", "+ ++a+b" ], + [ "+ ++a + b--", "+ ++a+b--" ], + [ "+ ++a + b++", "+ ++a+b++" ], + [ "+ ++a + + ++b", "+ ++a+ + ++b" ], + [ "+ ++a + + --b", "+ ++a+ +--b" ], + [ "+ ++a + + b", "+ ++a+ +b" ], + [ "+ ++a + + b--", "+ ++a+ +b--" ], + [ "+ ++a + + b++", "+ ++a+ +b++" ], + [ "+ ++a + - ++b", "+ ++a+-++b" ], + [ "+ ++a + - --b", "+ ++a+- --b" ], + [ "+ ++a + - b", "+ ++a+-b" ], + [ "+ ++a + - b--", "+ ++a+-b--" ], + [ "+ ++a + - b++", "+ ++a+-b++" ], + [ "+ ++a - ++b", "+ ++a-++b" ], + [ "+ ++a - --b", "+ ++a- --b" ], + [ "+ ++a - b", "+ ++a-b" ], + [ "+ ++a - b--", "+ ++a-b--" ], + [ "+ ++a - b++", "+ ++a-b++" ], + [ "+ ++a - + ++b", "+ ++a-+ ++b" ], + [ "+ ++a - + --b", "+ ++a-+--b" ], + [ "+ ++a - + b", "+ ++a-+b" ], + [ "+ ++a - + b--", "+ ++a-+b--" ], + [ "+ ++a - + b++", "+ ++a-+b++" ], + [ "+ ++a - - ++b", "+ ++a- -++b" ], + [ "+ ++a - - --b", "+ ++a- - --b" ], + [ "+ ++a - - b", "+ ++a- -b" ], + [ "+ ++a - - b--", "+ ++a- -b--" ], + [ "+ ++a - - b++", "+ ++a- -b++" ], + [ "+ --a + ++b", "+--a+ ++b" ], + [ "+ --a + --b", "+--a+--b" ], + [ "+ --a + b", "+--a+b" ], + [ "+ --a + b--", "+--a+b--" ], + [ "+ --a + b++", "+--a+b++" ], + [ "+ --a + + ++b", "+--a+ + ++b" ], + [ "+ --a + + --b", "+--a+ +--b" ], + [ "+ --a + + b", "+--a+ +b" ], + [ "+ --a + + b--", "+--a+ +b--" ], + [ "+ --a + + b++", "+--a+ +b++" ], + [ "+ --a + - ++b", "+--a+-++b" ], + [ "+ --a + - --b", "+--a+- --b" ], + [ "+ --a + - b", "+--a+-b" ], + [ "+ --a + - b--", "+--a+-b--" ], + [ "+ --a + - b++", "+--a+-b++" ], + [ "+ --a - ++b", "+--a-++b" ], + [ "+ --a - --b", "+--a- --b" ], + [ "+ --a - b", "+--a-b" ], + [ "+ --a - b--", "+--a-b--" ], + [ "+ --a - b++", "+--a-b++" ], + [ "+ --a - + ++b", "+--a-+ ++b" ], + [ "+ --a - + --b", "+--a-+--b" ], + [ "+ --a - + b", "+--a-+b" ], + [ "+ --a - + b--", "+--a-+b--" ], + [ "+ --a - + b++", "+--a-+b++" ], + [ "+ --a - - ++b", "+--a- -++b" ], + [ "+ --a - - --b", "+--a- - --b" ], + [ "+ --a - - b", "+--a- -b" ], + [ "+ --a - - b--", "+--a- -b--" ], + [ "+ --a - - b++", "+--a- -b++" ], + [ "+ a + ++b", "+a+ ++b" ], + [ "+ a + --b", "+a+--b" ], + [ "+ a + b", "+a+b" ], + [ "+ a + b--", "+a+b--" ], + [ "+ a + b++", "+a+b++" ], + [ "+ a + + ++b", "+a+ + ++b" ], + [ "+ a + + --b", "+a+ +--b" ], + [ "+ a + + b", "+a+ +b" ], + [ "+ a + + b--", "+a+ +b--" ], + [ "+ a + + b++", "+a+ +b++" ], + [ "+ a + - ++b", "+a+-++b" ], + [ "+ a + - --b", "+a+- --b" ], + [ "+ a + - b", "+a+-b" ], + [ "+ a + - b--", "+a+-b--" ], + [ "+ a + - b++", "+a+-b++" ], + [ "+ a - ++b", "+a-++b" ], + [ "+ a - --b", "+a- --b" ], + [ "+ a - b", "+a-b" ], + [ "+ a - b--", "+a-b--" ], + [ "+ a - b++", "+a-b++" ], + [ "+ a - + ++b", "+a-+ ++b" ], + [ "+ a - + --b", "+a-+--b" ], + [ "+ a - + b", "+a-+b" ], + [ "+ a - + b--", "+a-+b--" ], + [ "+ a - + b++", "+a-+b++" ], + [ "+ a - - ++b", "+a- -++b" ], + [ "+ a - - --b", "+a- - --b" ], + [ "+ a - - b", "+a- -b" ], + [ "+ a - - b--", "+a- -b--" ], + [ "+ a - - b++", "+a- -b++" ], + [ "+ a-- + ++b", "+a--+ ++b" ], + [ "+ a-- + --b", "+a--+--b" ], + [ "+ a-- + b", "+a--+b" ], + [ "+ a-- + b--", "+a--+b--" ], + [ "+ a-- + b++", "+a--+b++" ], + [ "+ a-- + + ++b", "+a--+ + ++b" ], + [ "+ a-- + + --b", "+a--+ +--b" ], + [ "+ a-- + + b", "+a--+ +b" ], + [ "+ a-- + + b--", "+a--+ +b--" ], + [ "+ a-- + + b++", "+a--+ +b++" ], + [ "+ a-- + - ++b", "+a--+-++b" ], + [ "+ a-- + - --b", "+a--+- --b" ], + [ "+ a-- + - b", "+a--+-b" ], + [ "+ a-- + - b--", "+a--+-b--" ], + [ "+ a-- + - b++", "+a--+-b++" ], + [ "+ a-- - ++b", "+a---++b" ], + [ "+ a-- - --b", "+a--- --b" ], + [ "+ a-- - b", "+a---b" ], + [ "+ a-- - b--", "+a---b--" ], + [ "+ a-- - b++", "+a---b++" ], + [ "+ a-- - + ++b", "+a---+ ++b" ], + [ "+ a-- - + --b", "+a---+--b" ], + [ "+ a-- - + b", "+a---+b" ], + [ "+ a-- - + b--", "+a---+b--" ], + [ "+ a-- - + b++", "+a---+b++" ], + [ "+ a-- - - ++b", "+a--- -++b" ], + [ "+ a-- - - --b", "+a--- - --b" ], + [ "+ a-- - - b", "+a--- -b" ], + [ "+ a-- - - b--", "+a--- -b--" ], + [ "+ a-- - - b++", "+a--- -b++" ], + [ "+ a++ + ++b", "+a+++ ++b" ], + [ "+ a++ + --b", "+a+++--b" ], + [ "+ a++ + b", "+a+++b" ], + [ "+ a++ + b--", "+a+++b--" ], + [ "+ a++ + b++", "+a+++b++" ], + [ "+ a++ + + ++b", "+a+++ + ++b" ], + [ "+ a++ + + --b", "+a+++ +--b" ], + [ "+ a++ + + b", "+a+++ +b" ], + [ "+ a++ + + b--", "+a+++ +b--" ], + [ "+ a++ + + b++", "+a+++ +b++" ], + [ "+ a++ + - ++b", "+a+++-++b" ], + [ "+ a++ + - --b", "+a+++- --b" ], + [ "+ a++ + - b", "+a+++-b" ], + [ "+ a++ + - b--", "+a+++-b--" ], + [ "+ a++ + - b++", "+a+++-b++" ], + [ "+ a++ - ++b", "+a++-++b" ], + [ "+ a++ - --b", "+a++- --b" ], + [ "+ a++ - b", "+a++-b" ], + [ "+ a++ - b--", "+a++-b--" ], + [ "+ a++ - b++", "+a++-b++" ], + [ "+ a++ - + ++b", "+a++-+ ++b" ], + [ "+ a++ - + --b", "+a++-+--b" ], + [ "+ a++ - + b", "+a++-+b" ], + [ "+ a++ - + b--", "+a++-+b--" ], + [ "+ a++ - + b++", "+a++-+b++" ], + [ "+ a++ - - ++b", "+a++- -++b" ], + [ "+ a++ - - --b", "+a++- - --b" ], + [ "+ a++ - - b", "+a++- -b" ], + [ "+ a++ - - b--", "+a++- -b--" ], + [ "+ a++ - - b++", "+a++- -b++" ], + [ "- ++a + ++b", "-++a+ ++b" ], + [ "- ++a + --b", "-++a+--b" ], + [ "- ++a + b", "-++a+b" ], + [ "- ++a + b--", "-++a+b--" ], + [ "- ++a + b++", "-++a+b++" ], + [ "- ++a + + ++b", "-++a+ + ++b" ], + [ "- ++a + + --b", "-++a+ +--b" ], + [ "- ++a + + b", "-++a+ +b" ], + [ "- ++a + + b--", "-++a+ +b--" ], + [ "- ++a + + b++", "-++a+ +b++" ], + [ "- ++a + - ++b", "-++a+-++b" ], + [ "- ++a + - --b", "-++a+- --b" ], + [ "- ++a + - b", "-++a+-b" ], + [ "- ++a + - b--", "-++a+-b--" ], + [ "- ++a + - b++", "-++a+-b++" ], + [ "- ++a - ++b", "-++a-++b" ], + [ "- ++a - --b", "-++a- --b" ], + [ "- ++a - b", "-++a-b" ], + [ "- ++a - b--", "-++a-b--" ], + [ "- ++a - b++", "-++a-b++" ], + [ "- ++a - + ++b", "-++a-+ ++b" ], + [ "- ++a - + --b", "-++a-+--b" ], + [ "- ++a - + b", "-++a-+b" ], + [ "- ++a - + b--", "-++a-+b--" ], + [ "- ++a - + b++", "-++a-+b++" ], + [ "- ++a - - ++b", "-++a- -++b" ], + [ "- ++a - - --b", "-++a- - --b" ], + [ "- ++a - - b", "-++a- -b" ], + [ "- ++a - - b--", "-++a- -b--" ], + [ "- ++a - - b++", "-++a- -b++" ], + [ "- --a + ++b", "- --a+ ++b" ], + [ "- --a + --b", "- --a+--b" ], + [ "- --a + b", "- --a+b" ], + [ "- --a + b--", "- --a+b--" ], + [ "- --a + b++", "- --a+b++" ], + [ "- --a + + ++b", "- --a+ + ++b" ], + [ "- --a + + --b", "- --a+ +--b" ], + [ "- --a + + b", "- --a+ +b" ], + [ "- --a + + b--", "- --a+ +b--" ], + [ "- --a + + b++", "- --a+ +b++" ], + [ "- --a + - ++b", "- --a+-++b" ], + [ "- --a + - --b", "- --a+- --b" ], + [ "- --a + - b", "- --a+-b" ], + [ "- --a + - b--", "- --a+-b--" ], + [ "- --a + - b++", "- --a+-b++" ], + [ "- --a - ++b", "- --a-++b" ], + [ "- --a - --b", "- --a- --b" ], + [ "- --a - b", "- --a-b" ], + [ "- --a - b--", "- --a-b--" ], + [ "- --a - b++", "- --a-b++" ], + [ "- --a - + ++b", "- --a-+ ++b" ], + [ "- --a - + --b", "- --a-+--b" ], + [ "- --a - + b", "- --a-+b" ], + [ "- --a - + b--", "- --a-+b--" ], + [ "- --a - + b++", "- --a-+b++" ], + [ "- --a - - ++b", "- --a- -++b" ], + [ "- --a - - --b", "- --a- - --b" ], + [ "- --a - - b", "- --a- -b" ], + [ "- --a - - b--", "- --a- -b--" ], + [ "- --a - - b++", "- --a- -b++" ], + [ "- a + ++b", "-a+ ++b" ], + [ "- a + --b", "-a+--b" ], + [ "- a + b", "-a+b" ], + [ "- a + b--", "-a+b--" ], + [ "- a + b++", "-a+b++" ], + [ "- a + + ++b", "-a+ + ++b" ], + [ "- a + + --b", "-a+ +--b" ], + [ "- a + + b", "-a+ +b" ], + [ "- a + + b--", "-a+ +b--" ], + [ "- a + + b++", "-a+ +b++" ], + [ "- a + - ++b", "-a+-++b" ], + [ "- a + - --b", "-a+- --b" ], + [ "- a + - b", "-a+-b" ], + [ "- a + - b--", "-a+-b--" ], + [ "- a + - b++", "-a+-b++" ], + [ "- a - ++b", "-a-++b" ], + [ "- a - --b", "-a- --b" ], + [ "- a - b", "-a-b" ], + [ "- a - b--", "-a-b--" ], + [ "- a - b++", "-a-b++" ], + [ "- a - + ++b", "-a-+ ++b" ], + [ "- a - + --b", "-a-+--b" ], + [ "- a - + b", "-a-+b" ], + [ "- a - + b--", "-a-+b--" ], + [ "- a - + b++", "-a-+b++" ], + [ "- a - - ++b", "-a- -++b" ], + [ "- a - - --b", "-a- - --b" ], + [ "- a - - b", "-a- -b" ], + [ "- a - - b--", "-a- -b--" ], + [ "- a - - b++", "-a- -b++" ], + [ "- a-- + ++b", "-a--+ ++b" ], + [ "- a-- + --b", "-a--+--b" ], + [ "- a-- + b", "-a--+b" ], + [ "- a-- + b--", "-a--+b--" ], + [ "- a-- + b++", "-a--+b++" ], + [ "- a-- + + ++b", "-a--+ + ++b" ], + [ "- a-- + + --b", "-a--+ +--b" ], + [ "- a-- + + b", "-a--+ +b" ], + [ "- a-- + + b--", "-a--+ +b--" ], + [ "- a-- + + b++", "-a--+ +b++" ], + [ "- a-- + - ++b", "-a--+-++b" ], + [ "- a-- + - --b", "-a--+- --b" ], + [ "- a-- + - b", "-a--+-b" ], + [ "- a-- + - b--", "-a--+-b--" ], + [ "- a-- + - b++", "-a--+-b++" ], + [ "- a-- - ++b", "-a---++b" ], + [ "- a-- - --b", "-a--- --b" ], + [ "- a-- - b", "-a---b" ], + [ "- a-- - b--", "-a---b--" ], + [ "- a-- - b++", "-a---b++" ], + [ "- a-- - + ++b", "-a---+ ++b" ], + [ "- a-- - + --b", "-a---+--b" ], + [ "- a-- - + b", "-a---+b" ], + [ "- a-- - + b--", "-a---+b--" ], + [ "- a-- - + b++", "-a---+b++" ], + [ "- a-- - - ++b", "-a--- -++b" ], + [ "- a-- - - --b", "-a--- - --b" ], + [ "- a-- - - b", "-a--- -b" ], + [ "- a-- - - b--", "-a--- -b--" ], + [ "- a-- - - b++", "-a--- -b++" ], + [ "- a++ + ++b", "-a+++ ++b" ], + [ "- a++ + --b", "-a+++--b" ], + [ "- a++ + b", "-a+++b" ], + [ "- a++ + b--", "-a+++b--" ], + [ "- a++ + b++", "-a+++b++" ], + [ "- a++ + + ++b", "-a+++ + ++b" ], + [ "- a++ + + --b", "-a+++ +--b" ], + [ "- a++ + + b", "-a+++ +b" ], + [ "- a++ + + b--", "-a+++ +b--" ], + [ "- a++ + + b++", "-a+++ +b++" ], + [ "- a++ + - ++b", "-a+++-++b" ], + [ "- a++ + - --b", "-a+++- --b" ], + [ "- a++ + - b", "-a+++-b" ], + [ "- a++ + - b--", "-a+++-b--" ], + [ "- a++ + - b++", "-a+++-b++" ], + [ "- a++ - ++b", "-a++-++b" ], + [ "- a++ - --b", "-a++- --b" ], + [ "- a++ - b", "-a++-b" ], + [ "- a++ - b--", "-a++-b--" ], + [ "- a++ - b++", "-a++-b++" ], + [ "- a++ - + ++b", "-a++-+ ++b" ], + [ "- a++ - + --b", "-a++-+--b" ], + [ "- a++ - + b", "-a++-+b" ], + [ "- a++ - + b--", "-a++-+b--" ], + [ "- a++ - + b++", "-a++-+b++" ], + [ "- a++ - - ++b", "-a++- -++b" ], + [ "- a++ - - --b", "-a++- - --b" ], + [ "- a++ - - b", "-a++- -b" ], + [ "- a++ - - b--", "-a++- -b--" ], + [ "- a++ - - b++", "-a++- -b++" ], + ].forEach(function(exp) { + assert.strictEqual(UglifyJS.parse(exp[0]).print_to_string(), exp[1] + ";"); + }); + }); +}); diff --git a/test/run-tests.js b/test/run-tests.js index a4721399..15a12c6b 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -194,6 +194,9 @@ function parse_test(file) { if (node instanceof U.AST_LabeledStatement && tw.parent() instanceof U.AST_Toplevel) { var name = node.label.name; + if (name in tests) { + throw new Error('Duplicated test name "' + name + '" in ' + file); + } tests[name] = get_one_test(name, node.body); return true; } diff --git a/tools/node.js b/tools/node.js index c68faaa5..108803e5 100644 --- a/tools/node.js +++ b/tools/node.js @@ -115,7 +115,7 @@ exports.minify = function(files, options) { // 5. output var inMap = options.inSourceMap; - var output = {}; + var output = { max_line_len: 32000 }; if (typeof options.inSourceMap == "string") { inMap = JSON.parse(fs.readFileSync(options.inSourceMap, "utf8")); } |