diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..31ba69d3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +- Bug report or feature request? +- `uglify-js` version (`uglifyjs -V`) +- JavaScript input - ideally as small as possible. +- The `uglifyjs` CLI command executed or `minify()` options used. +- An example of JavaScript output produced and/or the error or warning. + +Note: the release version of `uglify-js` only supports ES5. Those wishing to minify ES6 should use the experimental [`harmony`](https://github.com/mishoo/UglifyJS2#harmony) branch. diff --git a/.travis.yml b/.travis.yml index b91a80e9..b2aef3dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,13 @@ language: node_js before_install: "npm install -g npm" node_js: - - "0.12" - "0.10" + - "0.12" - "4" - "6" + - "7" +env: + - UGLIFYJS_TEST_ALL=1 matrix: fast_finish: true sudo: false diff --git a/README.md b/README.md index 6dab6ff4..4c47a097 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,6 @@ The available options are: --support-ie8 Use this flag to support Internet Explorer 6/7/8. Equivalent to setting `screw_ie8: false` in `minify()` for `compress`, `mangle` and `output` options. - Note: `--support-ie8` may generate incorrect code - for `try`/`catch` in ES5 compliant browsers. --expr Parse a single expression, rather than a program (for parsing JSON) -p, --prefix Skip prefix for original filenames that appear @@ -350,6 +348,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_math` (default: false) -- optimize numerical expressions like + `2 * x * 3` into `6 * x`, which may give imprecise floating point results. + - `unsafe_proto` (default: false) -- optimize expressions like `Array.prototype.slice.call(a)` into `[].slice.call(a)` @@ -390,11 +391,11 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `cascade` -- small optimization for sequences, transform `x, x` into `x` and `x = something(), x` into `x = something()` -- `collapse_vars` -- default `false`. Collapse single-use `var` and `const` - definitions when possible. +- `collapse_vars` -- Collapse single-use `var` and `const` definitions + when possible. -- `reduce_vars` -- default `false`. Improve optimization on variables assigned - with and used as constant values. +- `reduce_vars` -- Improve optimization on variables assigned with and + used as constant values. - `warnings` -- display warnings when dropping unreachable code or unused declarations etc. @@ -426,6 +427,10 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `drop_error` -- default `false`. Pass `true` to discard description of Error() calls. +- `expression` -- default `false`. Pass `true` to preserve completion values + from terminal statements without `return`, e.g. in bookmarklets. + + - `keep_fargs` -- default `true`. Prevents the compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. diff --git a/bin/uglifyjs b/bin/uglifyjs index 367d66e2..e39a4b4b 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -8,7 +8,6 @@ var sys = require("util"); var yargs = require("yargs"); var fs = require("fs"); var path = require("path"); -var async = require("async"); var acorn; var screw_ie8 = true; var ARGS = yargs @@ -27,7 +26,7 @@ mangling you need to use `-c` and `-m`.\ .describe("source-map-include-sources", "Pass this flag if you want to include the content of source files in the source map as sourcesContent property.") .describe("in-source-map", "Input source map, useful if you're compressing JS that was generated from some other original code.") .describe("screw-ie8", "Do not support Internet Explorer 6/7/8. This flag is enabled by default.") - .describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript. Note: may generate incorrect code for try/catch in ES5 compliant browsers.") + .describe("support-ie8", "Support non-standard Internet Explorer 6/7/8 javascript.") .describe("expr", "Parse a single expression, rather than a program (for parsing JSON)") .describe("p", "Skip prefix for original filenames that appear in source maps. \ For example -p 3 will drop 3 directories from file names and ensure they are relative paths. \ @@ -319,8 +318,11 @@ var STATS = {}; var TOPLEVEL = null; var P_RELATIVE = ARGS.p && ARGS.p == "relative"; var SOURCES_CONTENT = {}; +var index = 0; -async.eachLimit(files, 1, function (file, cb) { +!function cb() { + if (index == files.length) return done(); + var file = files[index++]; read_whole_file(file, function (err, code) { if (err) { print_error("ERROR: can't read file: " + file); @@ -388,7 +390,9 @@ async.eachLimit(files, 1, function (file, cb) { }); cb(); }); -}, function () { +}(); + +function done() { var OUTPUT_FILE = ARGS.o; var SOURCE_MAP = (ARGS.source_map || ARGS.source_map_inline) ? UglifyJS.SourceMap({ @@ -537,7 +541,7 @@ async.eachLimit(files, 1, function (file, cb) { })); } } -}); +} /* -----[ functions ]----- */ diff --git a/lib/ast.js b/lib/ast.js index f3df78fe..092a9590 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -91,9 +91,20 @@ var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos }, null); var AST_Node = DEFNODE("Node", "start end", { - clone: function() { + _clone: function(deep) { + if (deep) { + var self = this.clone(); + return self.transform(new TreeTransformer(function(node) { + if (node !== self) { + return node.clone(true); + } + })); + } return new this.CTOR(this); }, + clone: function(deep) { + return this._clone(deep); + }, $documentation: "Base class of all AST nodes", $propdoc: { start: "[AST_Token] The first token of this node", @@ -199,6 +210,20 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { this.label._walk(visitor); this.body._walk(visitor); }); + }, + clone: function(deep) { + var node = this._clone(deep); + if (deep) { + var refs = node.label.references; + var label = this.label; + node.walk(new TreeWalker(function(node) { + if (node instanceof AST_LoopControl + && node.label && node.label.thedef === label) { + refs.push(node); + } + })); + } + return node; } }, AST_StatementWithBody); @@ -812,9 +837,6 @@ var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", - $propdoc: { - init: "[AST_Node*/S] array of initializers for this declaration." - } }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { @@ -962,8 +984,8 @@ TreeWalker.prototype = { push: function (node) { if (node instanceof AST_Lambda) { this.directives = Object.create(this.directives); - } else if (node instanceof AST_Directive) { - this.directives[node.value] = this.directives[node.value] ? "up" : true; + } else if (node instanceof AST_Directive && !this.directives[node.value]) { + this.directives[node.value] = node; } this.stack.push(node); }, @@ -991,7 +1013,7 @@ TreeWalker.prototype = { for (var i = 0; i < node.body.length; ++i) { var st = node.body[i]; if (!(st instanceof AST_Directive)) break; - if (st.value == type) return true; + if (st.value == type) return st; } } }, diff --git a/lib/compress.js b/lib/compress.js index c27b2b96..03410eb2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -54,6 +54,7 @@ function Compressor(options, false_by_default) { drop_debugger : !false_by_default, unsafe : false, unsafe_comps : false, + unsafe_math : false, unsafe_proto : false, conditionals : !false_by_default, comparisons : !false_by_default, @@ -80,6 +81,7 @@ function Compressor(options, false_by_default) { drop_console : false, drop_error : false, angular : false, + expression : false, warnings : true, global_defs : {}, passes : 1, @@ -116,12 +118,18 @@ Compressor.prototype = new TreeTransformer; merge(Compressor.prototype, { option: function(key) { return this.options[key] }, compress: function(node) { + if (this.option("expression")) { + node = node.process_expression(true); + } var passes = +this.options.passes || 1; for (var pass = 0; pass < passes && pass < 3; ++pass) { if (pass > 0 || this.option("reduce_vars")) node.reset_opt_flags(this, true); node = node.transform(this); } + if (this.option("expression")) { + node = node.process_expression(false); + } return node; }, warn: function(text, props) { @@ -178,11 +186,56 @@ merge(Compressor.prototype, { return this.print_to_string() == node.print_to_string(); }); + AST_Node.DEFMETHOD("process_expression", function(insert) { + var self = this; + var tt = new TreeTransformer(function(node) { + if (insert && node instanceof AST_SimpleStatement) { + return make_node(AST_Return, node, { + value: node.body + }); + } + if (!insert && node instanceof AST_Return) { + return make_node(AST_SimpleStatement, node, { + body: node.value || make_node(AST_Undefined, node) + }); + } + if (node instanceof AST_Lambda && node !== self) { + return node; + } + if (node instanceof AST_Block) { + var index = node.body.length - 1; + if (index >= 0) { + node.body[index] = node.body[index].transform(tt); + } + } + if (node instanceof AST_If) { + node.body = node.body.transform(tt); + if (node.alternative) { + node.alternative = node.alternative.transform(tt); + } + } + if (node instanceof AST_With) { + node.body = node.body.transform(tt); + } + return node; + }); + return self.transform(tt); + }); + AST_Node.DEFMETHOD("reset_opt_flags", function(compressor, rescan){ var reduce_vars = rescan && compressor.option("reduce_vars"); + var toplevel = compressor.option("toplevel"); + var ie8 = !compressor.option("screw_ie8"); var safe_ids = []; push(); - var tw = new TreeWalker(function(node){ + var suppressor = new TreeWalker(function(node) { + if (node instanceof AST_Symbol) { + var d = node.definition(); + if (node instanceof AST_SymbolRef) d.references.push(node); + d.fixed = false; + } + }); + var tw = new TreeWalker(function(node, descend){ if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; @@ -193,10 +246,14 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (!d.fixed || isModified(node, 0) || !is_safe(d)) { + if (!d.fixed || !is_safe(d) + || is_modified(node, 0, d.fixed instanceof AST_Lambda)) { d.fixed = false; } } + if (ie8 && node instanceof AST_SymbolCatch) { + node.definition().fixed = false; + } if (node instanceof AST_VarDef) { var d = node.name.definition(); if (d.fixed === undefined) { @@ -206,6 +263,21 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (node instanceof AST_Defun) { + var d = node.name.definition(); + if (!toplevel && d.global || is_safe(d)) { + d.fixed = false; + } else { + d.fixed = node; + mark_as_safe(d); + } + var save_ids = safe_ids; + safe_ids = []; + push(); + descend(); + safe_ids = save_ids; + return true; + } var iife; if (node instanceof AST_Function && (iife = tw.parent()) instanceof AST_Call @@ -244,15 +316,19 @@ merge(Compressor.prototype, { return true; } if (node instanceof AST_ForIn) { - if (node.init instanceof AST_SymbolRef) { - node.init.definition().fixed = false; - } + node.init.walk(suppressor); node.object.walk(tw); push(); node.body.walk(tw); pop(); return true; } + if (node instanceof AST_Catch) { + push(); + descend(); + pop(); + return true; + } } }); this.walk(tw); @@ -276,18 +352,22 @@ merge(Compressor.prototype, { } function reset_def(def) { - def.fixed = undefined; + if (toplevel || !def.global || def.orig[0] instanceof AST_SymbolConst) { + def.fixed = undefined; + } else { + def.fixed = false; + } def.references = []; def.should_replace = undefined; } - function isModified(node, level) { + function is_modified(node, level, func) { var parent = tw.parent(level); if (isLHS(node, parent) - || parent instanceof AST_Call && parent.expression === node) { + || !func && parent instanceof AST_Call && parent.expression === node) { return true; } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return isModified(parent, level + 1); + return !func && is_modified(parent, level + 1); } } }); @@ -477,8 +557,13 @@ merge(Compressor.prototype, { // Constant single use vars can be replaced in any scope. if (var_decl.value.is_constant()) { var ctt = new TreeTransformer(function(node) { + var parent = ctt.parent(); + if (parent instanceof AST_IterationStatement + && (parent.condition === node || parent.init === node)) { + return node; + } if (node === ref) - return replace_var(node, ctt.parent(), true); + return replace_var(node, parent, true); }); stat.transform(ctt); continue; @@ -547,10 +632,7 @@ merge(Compressor.prototype, { return statements; function is_lvalue(node, parent) { - return node instanceof AST_SymbolRef && ( - (parent instanceof AST_Assign && node === parent.left) - || (parent instanceof AST_Unary && parent.expression === node - && (parent.operator == "++" || parent.operator == "--"))); + return node instanceof AST_SymbolRef && isLHS(node, parent); } function replace_var(node, parent, is_constant) { if (is_lvalue(node, parent)) return node; @@ -567,7 +649,7 @@ merge(Compressor.prototype, { // Further optimize statement after substitution. stat.reset_opt_flags(compressor); - compressor.warn("Replacing " + (is_constant ? "constant" : "variable") + + compressor.warn("Collapsing " + (is_constant ? "constant" : "variable") + " " + var_name + " [{file}:{line},{col}]", node.start); CHANGED = true; return value; @@ -715,7 +797,7 @@ merge(Compressor.prototype, { CHANGED = true; stat = stat.clone(); stat.alternative = ret[0] || make_node(AST_Return, stat, { - value: make_node(AST_Undefined, stat) + value: null }); ret[0] = stat.transform(compressor); continue loop; @@ -748,7 +830,7 @@ merge(Compressor.prototype, { && !stat.alternative) { CHANGED = true; ret.push(make_node(AST_Return, ret[0], { - value: make_node(AST_Undefined, ret[0]) + value: null }).transform(compressor)); ret.unshift(stat); continue loop; @@ -1005,6 +1087,10 @@ merge(Compressor.prototype, { })); }; + function is_undefined(node) { + return node instanceof AST_Undefined || node.is_undefined; + } + /* -----[ boolean/negation helpers ]----- */ // methods to determine whether an expression has a boolean result type @@ -1035,6 +1121,34 @@ merge(Compressor.prototype, { node.DEFMETHOD("is_boolean", func); }); + // methods to determine if an expression has a numeric result type + (function (def){ + def(AST_Node, return_false); + def(AST_Number, return_true); + var unary = makePredicate("+ - ~ ++ --"); + def(AST_Unary, function(){ + return unary(this.operator); + }); + var binary = makePredicate("- * / % & | ^ << >> >>>"); + def(AST_Binary, function(compressor){ + return binary(this.operator) || this.operator == "+" + && this.left.is_number(compressor) + && this.right.is_number(compressor); + }); + var assign = makePredicate("-= *= /= %= &= |= ^= <<= >>= >>>="); + def(AST_Assign, function(compressor){ + return assign(this.operator) || this.right.is_number(compressor); + }); + def(AST_Seq, function(compressor){ + return this.cdr.is_number(compressor); + }); + def(AST_Conditional, function(compressor){ + return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); + }); + })(function(node, func){ + node.DEFMETHOD("is_number", func); + }); + // methods to determine if an expression has a string result type (function (def){ def(AST_Node, return_false); @@ -1060,7 +1174,7 @@ merge(Compressor.prototype, { }); function isLHS(node, parent) { - return parent instanceof AST_Unary && (parent.operator === "++" || parent.operator === "--") + return parent instanceof AST_Unary && (parent.operator == "++" || parent.operator == "--") || parent instanceof AST_Assign && parent.left === node; } @@ -1210,14 +1324,7 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - // XXX: AST_Accessor and AST_Function both inherit from AST_Scope, - // which itself inherits from AST_Statement; however, they aren't - // really statements. This could bite in other places too. :-( - // Wish JS had multiple inheritance. - def(AST_Accessor, function(){ - throw def; - }); - def(AST_Function, function(){ + def(AST_Lambda, function(){ throw def; }); function ev(node, compressor) { @@ -1734,6 +1841,7 @@ merge(Compressor.prototype, { } if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { var def = node.definitions.filter(function(def){ + if (def.value) def.value = def.value.transform(tt); if (def.name.definition().id in in_use_ids) return true; var w = { name : def.name.name, @@ -1794,14 +1902,17 @@ 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 (drop_vars && assign_as_unused) { + var n = node; + while (n instanceof AST_Assign + && n.operator == "=" + && n.left instanceof AST_SymbolRef) { + var def = n.left.definition(); + if (def.id in in_use_ids + || self.variables.get(def.name) !== def) break; + n = n.right; } + if (n !== node) return n; } if (node instanceof AST_For) { descend(node, this); @@ -1990,7 +2101,15 @@ merge(Compressor.prototype, { 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; + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) { + if (this.expression instanceof AST_Function + && (!this.expression.name || !this.expression.name.definition().references.length)) { + var node = this.clone(); + node.expression = node.expression.process_expression(false); + return node; + } + return this; + } if (this.pure) { compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); this.pure.value = this.pure.value.replace(/[@#]__PURE__/g, ' '); @@ -2133,7 +2252,7 @@ merge(Compressor.prototype, { } } else { // self instanceof AST_Do - return self.body; + return self; } } if (self instanceof AST_While) { @@ -2314,8 +2433,8 @@ merge(Compressor.prototype, { return make_node(self.body.CTOR, self, { value: make_node(AST_Conditional, self, { condition : self.condition, - consequent : self.body.value || make_node(AST_Undefined, self.body).optimize(compressor), - alternative : self.alternative.value || make_node(AST_Undefined, self.alternative).optimize(compressor) + consequent : self.body.value || make_node(AST_Undefined, self.body), + alternative : self.alternative.value || make_node(AST_Undefined, self.alternative) }) }).transform(compressor); } @@ -2482,12 +2601,28 @@ merge(Compressor.prototype, { }); OPT(AST_Call, function(self, compressor){ + var exp = self.expression; + if (compressor.option("reduce_vars") + && exp instanceof AST_SymbolRef) { + var def = exp.definition(); + if (def.fixed instanceof AST_Defun) { + def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true); + } + if (def.fixed instanceof AST_Function) { + exp = def.fixed; + if (compressor.option("unused") + && def.references.length == 1 + && compressor.find_parent(AST_Scope) === def.scope) { + self.expression = exp; + } + } + } if (compressor.option("unused") - && self.expression instanceof AST_Function - && !self.expression.uses_arguments - && !self.expression.uses_eval - && self.args.length > self.expression.argnames.length) { - var end = self.expression.argnames.length; + && exp instanceof AST_Function + && !exp.uses_arguments + && !exp.uses_eval + && self.args.length > exp.argnames.length) { + var end = exp.argnames.length; for (var i = end, len = self.args.length; i < len; i++) { var node = self.args[i].drop_side_effect_free(compressor); if (node) { @@ -2497,7 +2632,6 @@ merge(Compressor.prototype, { self.args.length = end; } if (compressor.option("unsafe")) { - var exp = self.expression; if (exp instanceof AST_SymbolRef && exp.undeclared()) { switch (exp.name) { case "Array": @@ -2687,16 +2821,24 @@ merge(Compressor.prototype, { self.args = []; } } - if (compressor.option("side_effects")) { - if (self.expression instanceof AST_Function - && self.args.length == 0 - && !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) { - return make_node(AST_Undefined, self).transform(compressor); + if (exp instanceof AST_Function) { + if (exp.body[0] instanceof AST_Return) { + var value = exp.body[0].value; + if (!value || value.is_constant()) { + var args = self.args.concat(value || make_node(AST_Undefined, self)); + return AST_Seq.from_array(args).transform(compressor); + } + } + if (compressor.option("side_effects")) { + if (!AST_Block.prototype.has_side_effects.call(exp, compressor)) { + var args = self.args.concat(make_node(AST_Undefined, self)); + return AST_Seq.from_array(args).transform(compressor); + } } } if (compressor.option("drop_console")) { - if (self.expression instanceof AST_PropAccess) { - var name = self.expression.expression; + if (exp instanceof AST_PropAccess) { + var name = exp.expression; while (name.expression) { name = name.expression; } @@ -2707,12 +2849,6 @@ merge(Compressor.prototype, { } } } - if (self.args.length == 0 - && self.expression instanceof AST_Function - && self.expression.body[0] instanceof AST_Return - && self.expression.body[0].value.is_constant()) { - return self.expression.body[0].value; - } if (compressor.option("negate_iife") && compressor.parent() instanceof AST_SimpleStatement && is_iife_call(self)) { @@ -2767,23 +2903,41 @@ merge(Compressor.prototype, { 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")) { + var left; if (self.car instanceof AST_Assign && !self.car.left.has_side_effects(compressor)) { - if (self.car.left.equivalent_to(self.cdr)) { - return self.car; - } - if (self.cdr instanceof AST_Call - && self.cdr.expression.equivalent_to(self.car.left)) { - self.cdr.expression = self.car; - return self.cdr; - } + left = self.car.left; + } else if (self.car instanceof AST_Unary + && (self.car.operator == "++" || self.car.operator == "--")) { + left = self.car.expression; } - if (!self.car.has_side_effects(compressor) - && self.car.equivalent_to(self.cdr)) { - return self.car; + if (left) { + var parent, field; + var cdr = self.cdr; + while (true) { + if (cdr.equivalent_to(left)) { + var car = self.car instanceof AST_UnaryPostfix ? make_node(AST_UnaryPrefix, self.car, { + operator: self.car.operator, + expression: left + }) : self.car; + if (parent) { + parent[field] = car; + return self.cdr; + } + return car; + } + if (cdr instanceof AST_Binary && !(cdr instanceof AST_Assign)) { + field = cdr.left.is_constant() ? "right" : "left"; + } else if (cdr instanceof AST_Call + || cdr instanceof AST_Unary && cdr.operator != "++" && cdr.operator != "--") { + field = "expression"; + } else break; + parent = cdr; + cdr = cdr[field]; + } } } - if (self.cdr instanceof AST_Undefined) { + if (is_undefined(self.cdr)) { return make_node(AST_UnaryPrefix, self, { operator : "void", expression : self.car @@ -2822,7 +2976,7 @@ merge(Compressor.prototype, { self.expression = e; return self; } else { - return make_node(AST_Undefined, self); + return make_node(AST_Undefined, self).transform(compressor); } } if (compressor.option("booleans") && compressor.in_boolean_context()) { @@ -2895,8 +3049,14 @@ merge(Compressor.prototype, { right: rhs[0] }).optimize(compressor); } - function reverse(op, force) { - if (force || !(self.left.has_side_effects(compressor) || self.right.has_side_effects(compressor))) { + function reversible() { + return self.left instanceof AST_Constant + || self.right instanceof AST_Constant + || !self.left.has_side_effects(compressor) + && !self.right.has_side_effects(compressor); + } + function reverse(op) { + if (reversible()) { if (op) self.operator = op; var tmp = self.left; self.left = self.right; @@ -2912,7 +3072,7 @@ merge(Compressor.prototype, { if (!(self.left instanceof AST_Binary && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { - reverse(null, true); + reverse(); } } if (/^[!=]==?$/.test(self.operator)) { @@ -2947,6 +3107,7 @@ merge(Compressor.prototype, { case "===": case "!==": if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || + (self.left.is_number(compressor) && self.right.is_number(compressor)) || (self.left.is_boolean() && self.right.is_boolean())) { self.operator = self.operator.substr(0, 2); } @@ -3084,7 +3245,10 @@ merge(Compressor.prototype, { } break; } - if (self.operator == "+") { + var associative = true; + switch (self.operator) { + case "+": + // "foo" + ("bar" + x) => "foobar" + x if (self.left instanceof AST_Constant && self.right instanceof AST_Binary && self.right.operator == "+" @@ -3092,7 +3256,7 @@ merge(Compressor.prototype, { && self.right.is_string(compressor)) { self = make_node(AST_Binary, self, { operator: "+", - left: make_node(AST_String, null, { + left: make_node(AST_String, self.left, { value: "" + self.left.getValue() + self.right.left.getValue(), start: self.left.start, end: self.right.left.end @@ -3100,6 +3264,7 @@ merge(Compressor.prototype, { right: self.right.right }); } + // (x + "foo") + "bar" => x + "foobar" if (self.right instanceof AST_Constant && self.left instanceof AST_Binary && self.left.operator == "+" @@ -3108,13 +3273,14 @@ merge(Compressor.prototype, { self = make_node(AST_Binary, self, { operator: "+", left: self.left.left, - right: make_node(AST_String, null, { + right: make_node(AST_String, self.right, { value: "" + self.left.right.getValue() + self.right.getValue(), start: self.left.right.start, end: self.right.end }) }); } + // (x + "foo") + ("bar" + y) => (x + "foobar") + y if (self.left instanceof AST_Binary && self.left.operator == "+" && self.left.is_string(compressor) @@ -3128,7 +3294,7 @@ merge(Compressor.prototype, { left: make_node(AST_Binary, self.left, { operator: "+", left: self.left.left, - right: make_node(AST_String, null, { + right: make_node(AST_String, self.left.right, { value: "" + self.left.right.getValue() + self.right.left.getValue(), start: self.left.right.start, end: self.right.left.end @@ -3137,6 +3303,122 @@ merge(Compressor.prototype, { right: self.right.right }); } + // a + -b => a - b + if (self.right instanceof AST_UnaryPrefix + && self.right.operator == "-" + && self.left.is_number(compressor)) { + self = make_node(AST_Binary, self, { + operator: "-", + left: self.left, + right: self.right.expression + }); + } + // -a + b => b - a + if (self.left instanceof AST_UnaryPrefix + && self.left.operator == "-" + && reversible() + && self.right.is_number(compressor)) { + self = make_node(AST_Binary, self, { + operator: "-", + left: self.right, + right: self.left.expression + }); + } + case "*": + associative = compressor.option("unsafe_math"); + case "&": + case "|": + case "^": + // a + +b => +b + a + if (self.left.is_number(compressor) + && self.right.is_number(compressor) + && reversible() + && !(self.left instanceof AST_Binary + && self.left.operator != self.operator + && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) { + var reversed = make_node(AST_Binary, self, { + operator: self.operator, + left: self.right, + right: self.left + }); + if (self.right instanceof AST_Constant + && !(self.left instanceof AST_Constant)) { + self = best_of(reversed, self); + } else { + self = best_of(self, reversed); + } + } + if (associative && self.is_number(compressor)) { + // a + (b + c) => (a + b) + c + if (self.right instanceof AST_Binary + && self.right.operator == self.operator) { + self = make_node(AST_Binary, self, { + operator: self.operator, + left: make_node(AST_Binary, self.left, { + operator: self.operator, + left: self.left, + right: self.right.left, + start: self.left.start, + end: self.right.left.end + }), + right: self.right.right + }); + } + // (n + 2) + 3 => 5 + n + // (2 * n) * 3 => 6 + n + if (self.right instanceof AST_Constant + && self.left instanceof AST_Binary + && self.left.operator == self.operator) { + if (self.left.left instanceof AST_Constant) { + self = make_node(AST_Binary, self, { + operator: self.operator, + left: make_node(AST_Binary, self.left, { + operator: self.operator, + left: self.left.left, + right: self.right, + start: self.left.left.start, + end: self.right.end + }), + right: self.left.right + }); + } else if (self.left.right instanceof AST_Constant) { + self = make_node(AST_Binary, self, { + operator: self.operator, + left: make_node(AST_Binary, self.left, { + operator: self.operator, + left: self.left.right, + right: self.right, + start: self.left.right.start, + end: self.right.end + }), + right: self.left.left + }); + } + } + // (a | 1) | (2 | d) => (3 | a) | b + if (self.left instanceof AST_Binary + && self.left.operator == self.operator + && self.left.right instanceof AST_Constant + && self.right instanceof AST_Binary + && self.right.operator == self.operator + && self.right.left instanceof AST_Constant) { + self = make_node(AST_Binary, self, { + operator: self.operator, + left: make_node(AST_Binary, self.left, { + operator: self.operator, + left: make_node(AST_Binary, self.left.left, { + operator: self.operator, + left: self.left.right, + right: self.right.left, + start: self.left.right.start, + end: self.right.left.end + }), + right: self.left.left + }), + right: self.right.right + }); + } + } } } // x && (y && z) ==> x && y && z @@ -3169,11 +3451,13 @@ merge(Compressor.prototype, { return def; } // testing against !self.scope.uses_with first is an optimization - if (self.undeclared() && !isLHS(self, compressor.parent()) + if (compressor.option("screw_ie8") + && 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); + return make_node(AST_Undefined, self).transform(compressor); case "NaN": return make_node(AST_NaN, self).transform(compressor); case "Infinity": @@ -3216,11 +3500,13 @@ merge(Compressor.prototype, { var scope = compressor.find_parent(AST_Scope); var undef = scope.find_variable("undefined"); if (undef) { - return make_node(AST_SymbolRef, self, { + var ref = make_node(AST_SymbolRef, self, { name : "undefined", scope : scope, thedef : undef }); + ref.is_undefined = true; + return ref; } } return self; @@ -3507,7 +3793,7 @@ merge(Compressor.prototype, { OPT(AST_RegExp, literals_in_boolean_context); OPT(AST_Return, function(self, compressor){ - if (self.value instanceof AST_Undefined) { + if (self.value && is_undefined(self.value)) { self.value = null; } return self; diff --git a/lib/output.js b/lib/output.js index 4a0a1e0e..767abd4d 100644 --- a/lib/output.js +++ b/lib/output.js @@ -46,17 +46,8 @@ var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/; function is_some_comments(comment) { - var text = comment.value; - var type = comment.type; - if (type == "comment2") { - // multiline comment - return /@preserve|@license|@cc_on/i.test(text); - } - return type == "comment5"; -} - -function is_comment5(comment) { - return comment.type == "comment5"; + // multiline comment + return comment.type == "comment2" && /@preserve|@license|@cc_on/i.test(comment.value); } function OutputStream(options) { @@ -86,7 +77,7 @@ function OutputStream(options) { }, true); // Convert comment option to RegExp if neccessary and set up comments filter - var comment_filter = options.shebang ? is_comment5 : return_false; // Default case, throw all comments away except shebangs + var comment_filter = return_false; // Default case, throw all comments away if (options.comments) { var comments = options.comments; if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) { @@ -98,12 +89,12 @@ function OutputStream(options) { } if (comments instanceof RegExp) { comment_filter = function(comment) { - return comment.type == "comment5" || comments.test(comment.value); + return comment.type != "comment5" && comments.test(comment.value); }; } else if (typeof comments === "function") { comment_filter = function(comment) { - return comment.type == "comment5" || comments(this, comment); + return comment.type != "comment5" && comments(this, comment); }; } else if (comments === "some") { @@ -400,10 +391,6 @@ function OutputStream(options) { return OUTPUT; }; - if (options.preamble) { - print(options.preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n")); - } - var stack = []; return { get : get, @@ -523,6 +510,17 @@ function OutputStream(options) { })); } + if (comments.length > 0 && output.pos() == 0) { + if (output.option("shebang") && comments[0].type == "comment5") { + output.print("#!" + comments.shift().value + "\n"); + output.indent(); + } + var preamble = output.option("preamble"); + if (preamble) { + output.print(preamble.replace(/\r\n?|[\n\u2028\u2029]|\s*$/g, "\n")); + } + } + comments = comments.filter(output.comment_filter, self); // Keep single line comments after nlb, after nlb @@ -547,10 +545,6 @@ function OutputStream(options) { output.space(); } } - else if (output.pos() === 0 && c.type == "comment5" && output.option("shebang")) { - output.print("#!" + c.value + "\n"); - output.indent(); - } }); } }); @@ -783,7 +777,7 @@ function OutputStream(options) { DEFPRINT(AST_Do, function(self, output){ output.print("do"); output.space(); - self._do_print_body(output); + make_block(self.body, output); output.space(); output.print("while"); output.space(); @@ -910,10 +904,10 @@ function OutputStream(options) { /* -----[ if ]----- */ function make_then(self, output) { - if (output.option("bracketize")) { - make_block(self.body, output); - return; - } + var b = self.body; + if (output.option("bracketize") + || !output.option("screw_ie8") && b instanceof AST_Do) + return make_block(b, output); // The squeezer replaces "block"-s that contain only a single // statement with the statement itself; technically, the AST // is correct, but this can create problems when we output an @@ -921,18 +915,7 @@ function OutputStream(options) { // IF *without* an ELSE block (then the outer ELSE would refer // to the inner IF). This function checks for this case and // adds the block brackets if needed. - if (!self.body) - return output.force_semicolon(); - if (self.body instanceof AST_Do) { - // Unconditionally use the if/do-while workaround for all browsers. - // https://github.com/mishoo/UglifyJS/issues/#issue/57 IE - // croaks with "syntax error" on code like this: if (foo) - // do ... while(cond); else ... we need block brackets - // around do/while - make_block(self.body, output); - return; - } - var b = self.body; + if (!b) return output.force_semicolon(); while (true) { if (b instanceof AST_If) { if (!b.alternative) { @@ -1341,15 +1324,7 @@ function OutputStream(options) { function force_statement(stat, output) { if (output.option("bracketize")) { - if (!stat || stat instanceof AST_EmptyStatement) - output.print("{}"); - else if (stat instanceof AST_BlockStatement) - stat.print(output); - else output.with_block(function(){ - output.indent(); - stat.print(output); - output.newline(); - }); + make_block(stat, output); } else { if (!stat || stat instanceof AST_EmptyStatement) output.force_semicolon(); @@ -1398,11 +1373,11 @@ function OutputStream(options) { }; function make_block(stmt, output) { - if (stmt instanceof AST_BlockStatement) { + if (!stmt || stmt instanceof AST_EmptyStatement) + output.print("{}"); + else if (stmt instanceof AST_BlockStatement) stmt.print(output); - return; - } - output.with_block(function(){ + else output.with_block(function(){ output.indent(); stmt.print(output); output.newline(); diff --git a/lib/parse.js b/lib/parse.js index 9b198ccd..25fe9319 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -558,6 +558,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { function next_token(force_regexp) { if (force_regexp != null) return read_regexp(force_regexp); + if (shebang && S.pos == 0 && looking_at("#!")) { + start_token(); + forward(2); + skip_line_comment("comment5"); + } for (;;) { skip_whitespace(); start_token(); @@ -589,13 +594,6 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (PUNC_CHARS(ch)) return token("punc", next()); if (OPERATOR_CHARS(ch)) return read_operator(); if (code == 92 || is_identifier_start(code)) return read_word(); - if (shebang) { - if (S.pos == 0 && looking_at("#!")) { - forward(2); - skip_line_comment("comment5"); - continue; - } - } break; } parse_error("Unexpected character '" + ch + "'"); @@ -930,11 +928,9 @@ function parse($TEXT, options) { expression : parenthesised(), body : statement() }); - - default: - unexpected(); } } + unexpected(); }); function labeled_statement() { diff --git a/lib/scope.js b/lib/scope.js index ae0c5777..483503ee 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -97,7 +97,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var labels = new Dictionary(); var defun = null; var tw = new TreeWalker(function(node, descend){ - if (options.screw_ie8 && node instanceof AST_Catch) { + if (node instanceof AST_Catch) { var save_scope = scope; scope = new AST_Scope(node); scope.init_scope_vars(); @@ -154,12 +154,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ } else if (node instanceof AST_SymbolVar || node instanceof AST_SymbolConst) { - var def = defun.def_variable(node); - def.init = tw.parent().value; + defun.def_variable(node); } else if (node instanceof AST_SymbolCatch) { - (options.screw_ie8 ? scope : defun) - .def_variable(node); + scope.def_variable(node); } else if (node instanceof AST_LabelRef) { var sym = labels.get(node.name); @@ -209,6 +207,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ }); self.walk(tw); + // pass 3: fix up any scoping issue with IE8 + if (!options.screw_ie8) { + self.walk(new TreeWalker(function(node, descend) { + if (node instanceof AST_SymbolCatch) { + var name = node.name; + var refs = node.thedef.references; + var scope = node.thedef.scope.parent_scope; + var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node); + refs.forEach(function(ref) { + ref.thedef = def; + ref.reference(options); + }); + node.thedef = def; + return true; + } + })); + } + if (options.cache) { this.cname = options.cache.cname; } diff --git a/lib/utils.js b/lib/utils.js index da663546..fdb20471 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -126,9 +126,11 @@ function merge(obj, ext) { return count; }; -function noop() {}; +function noop() {} function return_false() { return false; } function return_true() { return true; } +function return_this() { return this; } +function return_null() { return null; } var MAP = (function(){ function MAP(a, f, backwards) { diff --git a/package.json b/package.json index 33bfc236..2ba24c7c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "2.8.4", + "version": "2.8.12", "engines": { "node": ">=0.8.0" }, @@ -29,7 +29,6 @@ "LICENSE" ], "dependencies": { - "async": "~0.2.6", "source-map": "~0.5.1", "uglify-to-browserify": "~1.0.0", "yargs": "~3.10.0" diff --git a/test/benchmark.js b/test/benchmark.js index dc176a88..c150e5cf 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -24,26 +24,57 @@ var results = {}; var remaining = 2 * urls.length; function done() { if (!--remaining) { + var failures = []; urls.forEach(function(url) { + var info = results[url]; console.log(); console.log(url); - console.log(results[url].time); - console.log("SHA1:", results[url].sha1); + console.log(info.log); + var elapsed = 0; + info.log.replace(/: ([0-9]+\.[0-9]{3})s/g, function(match, time) { + elapsed += parseFloat(time); + }); + console.log("Run-time:", elapsed.toFixed(3), "s"); + console.log("Original:", info.input, "bytes"); + console.log("Uglified:", info.output, "bytes"); + console.log("SHA1 sum:", info.sha1); + if (info.code) { + failures.push(url); + } }); + if (failures.length) { + console.error("Benchmark failed:"); + failures.forEach(function(url) { + console.error(url); + }); + process.exit(1); + } } } urls.forEach(function(url) { - results[url] = { time: "" }; + results[url] = { + input: 0, + output: 0, + log: "" + }; 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) { + res.on("data", function(data) { + results[url].input += data.length; + }).pipe(uglifyjs.stdin); + uglifyjs.stdout.on("data", function(data) { + results[url].output += data.length; + }).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) + results[url].log += data; + }); + uglifyjs.on("exit", function(code) { + results[url].code = code; + done(); + }); }); }); diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 5f63488f..558a9ee0 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -344,9 +344,9 @@ collapse_vars_do_while: { } input: { function f1(y) { - // The constant do-while condition `c` will be replaced. + // The constant do-while condition `c` will not be replaced. var c = 9; - do { } while (c === 77); + do {} while (c === 77); } function f2(y) { // The non-constant do-while condition `c` will not be replaced. @@ -381,7 +381,8 @@ collapse_vars_do_while: { } expect: { function f1(y) { - do ; while (false); + var c = 9; + do ; while (77 === c); } function f2(y) { var c = 5 - y; @@ -418,9 +419,9 @@ collapse_vars_do_while_drop_assign: { } input: { function f1(y) { - // The constant do-while condition `c` will be replaced. + // The constant do-while condition `c` will be not replaced. var c = 9; - do { } while (c === 77); + do {} while (c === 77); } function f2(y) { // The non-constant do-while condition `c` will not be replaced. @@ -455,7 +456,8 @@ collapse_vars_do_while_drop_assign: { } expect: { function f1(y) { - do ; while (false); + var c = 9; + do ; while (77 === c); } function f2(y) { var c = 5 - y; @@ -1150,7 +1152,8 @@ collapse_vars_arguments: { options = { collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, - keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true, + toplevel:true } input: { var outer = function() { @@ -1309,9 +1312,99 @@ collapse_vars_regexp: { }; } (function(){ - var result, rx = /ab*/g; - while (result = rx.exec('acdabcdeabbb')) + var result, s = "acdabcdeabbb", rx = /ab*/g; + while (result = rx.exec(s)) console.log(result[0]); })(); } } + +issue_1537: { + options = { + collapse_vars: true, + } + input: { + var k = ''; + for (k in {prop: 'val'}){} + } + expect: { + var k = ''; + for (k in {prop: 'val'}); + } +} + +issue_1562: { + options = { + collapse_vars: true, + toplevel: true, + } + input: { + var v = 1, B = 2; + for (v in objs) f(B); + + var x = 3, C = 10; + while(x + 2) bar(C); + + var y = 4, D = 20; + do bar(D); while(y + 2); + + var z = 5, E = 30; + for (; f(z + 2) ;) bar(E); + } + expect: { + var v = 1; + for (v in objs) f(2); + + var x = 3; + while(x + 2) bar(10); + + var y = 4; + do bar(20); while(y + 2); + + var z = 5; + for (; f(z + 2) ;) bar(30); + } +} + +issue_1605_1: { + options = { + collapse_vars: true, + toplevel: false, + } + input: { + function foo(x) { + var y = x; + return y; + } + var o = new Object; + o.p = 1; + } + expect: { + function foo(x) { + return x; + } + var o = new Object; + o.p = 1; + } +} + +issue_1605_2: { + options = { + collapse_vars: true, + toplevel: "vars", + } + input: { + function foo(x) { + var y = x; + return y; + } + var o = new Object; + o.p = 1; + } + expect: { + function foo(x) { + return x; + } + (new Object).p = 1; + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index cd96d02d..45b7af6e 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -123,6 +123,7 @@ dead_code_const_annotation: { conditionals : true, evaluate : true, reduce_vars : true, + toplevel : true, }; input: { var unused; @@ -172,6 +173,7 @@ dead_code_const_annotation_complex_scope: { conditionals : true, evaluate : true, reduce_vars : true, + toplevel : true, }; input: { var unused_var; diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index c1ca1b55..9f3bf77d 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -632,7 +632,7 @@ iife: { } expect: { function f() { - ~function() {}(b); + b; } } } @@ -679,3 +679,115 @@ const_assign: { } } } + +issue_1539: { + options = { + cascade: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + function f() { + var a, b; + a = b = 42; + return a; + } + } + expect: { + function f() { + return 42; + } + } +} + +vardef_value: { + options = { + keep_fnames: false, + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g(){ + return x(); + } + var a = g(); + return a(42); + } + } + expect: { + function f() { + var a = function(){ + return x(); + }(); + return a(42); + } + } +} + +assign_binding: { + options = { + cascade: true, + side_effects: true, + unused: true, + } + input: { + function f() { + var a; + a = f.g, a(); + } + } + expect: { + function f() { + (0, f.g)(); + } + } +} + +assign_chain: { + options = { + unused: true, + } + input: { + function f() { + var a, b; + x = a = y = b = 42; + } + } + expect: { + function f() { + x = y = 42; + } + } +} + +issue_1583: { + options = { + keep_fargs: true, + reduce_vars: true, + unused: true, + } + input: { + function m(t) { + (function(e) { + t = e(); + })(function() { + return (function(a) { + return a; + })(function(a) {}); + }); + } + } + expect: { + function m(t) { + (function(e) { + t = (function() { + return (function(a) { + return a; + })(function(a) {}); + })(); + })(); + } + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 26b6e489..68739503 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -640,9 +640,7 @@ call_args: { expect: { const a = 1; console.log(1); - +function(a) { - return 1; - }(1); + +(1, 1); } } @@ -663,9 +661,7 @@ call_args_drop_param: { expect: { const a = 1; console.log(1); - +function() { - return 1; - }(b); + +(b, 1); } } diff --git a/test/compress/functions.js b/test/compress/functions.js index d3d99b57..a1a515a1 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -35,9 +35,9 @@ iifes_returning_constants_keep_fargs_true: { console.log("okay"); console.log(123); console.log(void 0); - console.log(function(x,y,z){return 2}(1,2,3)); - console.log(function(x,y){return 6}(2,3)); - console.log(function(x, y){return 6}(2,3,a(),b())); + console.log(2); + console.log(6); + console.log((a(), b(), 6)); } } @@ -71,6 +71,23 @@ iifes_returning_constants_keep_fargs_false: { console.log(void 0); console.log(2); console.log(6); - console.log(function(){return 6}(a(),b())); + console.log((a(), b(), 6)); + } +} + +issue_485_crashing_1530: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + } + input: { + (function(a) { + if (true) return; + var b = 42; + })(this); + } + expect: { + this, void 0; } } diff --git a/test/compress/issue-1443.js b/test/compress/issue-1443.js index a2565872..304a71ac 100644 --- a/test/compress/issue-1443.js +++ b/test/compress/issue-1443.js @@ -2,6 +2,7 @@ unsafe_undefined: { options = { + conditionals: true, if_return: true, unsafe: true } @@ -19,12 +20,7 @@ unsafe_undefined: { expect: { function f(n) { return function() { - if (a) - return b; - if (c) - return d; - else - return n; + return a ? b : c ? d : n; }; } } @@ -32,6 +28,7 @@ unsafe_undefined: { keep_fnames: { options = { + conditionals: true, if_return: true, unsafe: true } @@ -57,12 +54,7 @@ keep_fnames: { function n(n) { return n * n; } - if (a) - return b; - if (c) - return d; - else - return r; + return a ? b : c ? d : r; }; } } diff --git a/test/compress/issue-1569.js b/test/compress/issue-1569.js new file mode 100644 index 00000000..5f0bca34 --- /dev/null +++ b/test/compress/issue-1569.js @@ -0,0 +1,19 @@ +inner_reference: { + options = { + side_effects: true, + } + input: { + !function f(a) { + return a && f(a - 1) + a; + }(42); + !function g(a) { + return a; + }(42); + } + expect: { + !function f(a) { + return a && f(a - 1) + a; + }(42); + !void 0; + } +} diff --git a/test/compress/issue-1588.js b/test/compress/issue-1588.js new file mode 100644 index 00000000..ff25635c --- /dev/null +++ b/test/compress/issue-1588.js @@ -0,0 +1,87 @@ +screw_ie8: { + options = { + screw_ie8: true, + } + mangle = { + screw_ie8: true, + } + input: { + try { throw "foo"; } catch (x) { console.log(x); } + } + expect_exact: 'try{throw"foo"}catch(o){console.log(o)}' + expect_stdout: [ + "foo" + ] +} + +support_ie8: { + options = { + screw_ie8: false, + } + mangle = { + screw_ie8: false, + } + input: { + try { throw "foo"; } catch (x) { console.log(x); } + } + expect_exact: 'try{throw"foo"}catch(x){console.log(x)}' + expect_stdout: "foo" +} + +safe_undefined: { + options = { + conditionals: true, + if_return: true, + unsafe: false, + } + mangle = {} + input: { + var a, c; + console.log(function(undefined) { + return function() { + if (a) + return b; + if (c) + return d; + }; + }(1)()); + } + expect: { + var a, c; + console.log(function(n) { + return function() { + return a ? b : c ? d : void 0; + }; + }(1)()); + } + expect_stdout: true +} + +unsafe_undefined: { + options = { + conditionals: true, + if_return: true, + unsafe: true, + } + mangle = {} + input: { + var a, c; + console.log(function(undefined) { + return function() { + if (a) + return b; + if (c) + return d; + }; + }()()); + } + expect: { + var a, c; + console.log(function(n) { + return function() { + return a ? b : c ? d : n; + }; + }()()); + } + expect_stdout: true +} diff --git a/test/compress/issue-1609.js b/test/compress/issue-1609.js new file mode 100644 index 00000000..577a3ee1 --- /dev/null +++ b/test/compress/issue-1609.js @@ -0,0 +1,56 @@ +chained_evaluation_1: { + options = { + collapse_vars: true, + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + (function() { + var a = 1; + (function() { + var b = a, c; + c = f(b); + c.bar = b; + })(); + })(); + } + expect: { + (function() { + (function() { + var c; + c = f(1); + c.bar = 1; + })(); + })(); + } +} + +chained_evaluation_2: { + options = { + collapse_vars: true, + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + (function() { + var a = "long piece of string"; + (function() { + var b = a, c; + c = f(b); + c.bar = b; + })(); + })(); + } + expect: { + (function() { + var a = "long piece of string"; + (function() { + var c; + c = f(a); + c.bar = a; + })(); + })(); + } +} diff --git a/test/compress/issue-368.js b/test/compress/issue-368.js new file mode 100644 index 00000000..5960aa64 --- /dev/null +++ b/test/compress/issue-368.js @@ -0,0 +1,55 @@ +collapse: { + options = { + cascade: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + function f1() { + var a; + a = typeof b === 'function' ? b() : b; + return a !== undefined && c(); + } + function f2(b) { + var a; + b = c(); + a = typeof b === 'function' ? b() : b; + return 'stirng' == typeof a && d(); + } + function f3(c) { + var a; + a = b(a / 2); + if (a < 0) { + a++; + ++c; + return c / 2; + } + } + function f4(c) { + var a; + a = b(a / 2); + if (a < 0) { + a++; + c++; + return c / 2; + } + } + } + expect: { + function f1() { + return void 0 !== ('function' === typeof b ? b() : b) && c(); + } + function f2(b) { + return b = c(), 'stirng' == typeof ('function' === typeof b ? b() : b) && d(); + } + function f3(c) { + var a; + if ((a = b(a / 2)) < 0) return a++, ++c / 2; + } + function f4(c) { + var a; + if ((a = b(a / 2)) < 0) return a++, ++c / 2; + } + } +} diff --git a/test/compress/issue-640.js b/test/compress/issue-640.js new file mode 100644 index 00000000..dd3f3f21 --- /dev/null +++ b/test/compress/issue-640.js @@ -0,0 +1,317 @@ +cond_5: { + options = { + conditionals: true, + expression: true, + } + input: { + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } else { + alternate(); + } + } else { + alternate(); + } + + if (some_condition()) { + if (some_other_condition()) { + do_something(); + } + } + } + expect: { + some_condition() && some_other_condition() ? do_something() : alternate(); + if (some_condition() && some_other_condition()) do_something(); + } +} + +dead_code_const_annotation_regex: { + options = { + booleans : true, + conditionals : true, + dead_code : true, + evaluate : true, + expression : true, + loops : 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; + if (CONST_FOO_ANN) console.log('reachable'); + } +} + +drop_console_2: { + options = { + drop_console: true, + expression: 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_value: { + options = { + expression: true, + side_effects: true, + } + input: { + (1, [2, foo()], 3, {a:1, b:bar()}); + } + expect: { + foo(), {a:1, b:bar()}; + } +} + +wrongly_optimized: { + options = { + conditionals: true, + booleans: true, + evaluate: true, + expression: true, + } + input: { + function func() { + foo(); + } + if (func() || true) { + bar(); + } + } + expect: { + function func() { + foo(); + } + // TODO: optimize to `func(), bar()` + if (func(), !0) bar(); + } +} + +negate_iife_1: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function(){ stuff() })(); + } + expect: { + (function(){ stuff() })(); + } +} + +negate_iife_3: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false); + } +} + +negate_iife_3_off: { + options = { + conditionals: true, + expression: true, + negate_iife: false, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false); + } +} + +negate_iife_4: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + sequences: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? console.log(true) : console.log(false), function(){ + console.log("something"); + }(); + } +} + +negate_iife_5: { + options = { + conditionals: true, + expression: true, + negate_iife: true, + sequences: true, + } + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? foo(true) : bar(false), function(){ + console.log("something"); + }(); + } +} + +negate_iife_5_off: { + options = { + conditionals: true, + expression: true, + negate_iife: false, + sequences: true, + }; + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + (function(){ return t })() ? foo(true) : bar(false), function(){ + console.log("something"); + }(); + } +} + +issue_1254_negate_iife_true: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: '(function(){return function(){console.log("test")}})()();' +} + +issue_1254_negate_iife_nested: { + options = { + expression: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()()()()(); + } + expect_exact: '(function(){return function(){console.log("test")}})()()()()();' +} + +conditional: { + options = { + expression: true, + 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(); + pure(3 ? 4 : 5); + } +} + +limit_1: { + options = { + expression: true, + sequences: 3, + } + input: { + a; + b; + c; + d; + e; + f; + g; + h; + i; + j; + k; + } + expect: { + // Turned into a single return statement + // so it can no longer be split into lines + a,b,c,d,e,f,g,h,i,j,k; + } +} + +iife: { + options = { + expression: true, + 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() {}(), typeof function g() {}(); + } +} diff --git a/test/compress/issue-973.js b/test/compress/issue-973.js index 0e040922..30f886a8 100644 --- a/test/compress/issue-973.js +++ b/test/compress/issue-973.js @@ -50,6 +50,7 @@ this_binding_conditionals: { this_binding_collapse_vars: { options = { collapse_vars: true, + toplevel: true, }; input: { var c = a; c(); diff --git a/test/compress/loops.js b/test/compress/loops.js index ca05461c..7581e758 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -213,6 +213,228 @@ evaluate: { a(); for(;;) c(); - d(); + // rule disabled due to issue_1532 + do d(); while (false); } } + +issue_1532: { + options = { + evaluate: true, + loops: true, + } + input: { + function f(x, y) { + do { + if (x) break; + foo(); + } while (false); + } + } + expect: { + function f(x, y) { + do { + if (x) break; + foo(); + } while (false); + } + } +} + +issue_186: { + beautify = { + beautify: false, + screw_ie8: true, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x=3;if(foo())do{do{alert(x)}while(--x)}while(x);else bar();' +} + +issue_186_ie8: { + beautify = { + beautify: false, + screw_ie8: false, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x=3;if(foo()){do{do{alert(x)}while(--x)}while(x)}else bar();' +} + +issue_186_beautify: { + beautify = { + beautify: true, + screw_ie8: true, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: [ + 'var x = 3;', + '', + 'if (foo()) do {', + ' do {', + ' alert(x);', + ' } while (--x);', + '} while (x); else bar();', + ] +} + +issue_186_beautify_ie8: { + beautify = { + beautify: true, + screw_ie8: false, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: [ + 'var x = 3;', + '', + 'if (foo()) {', + ' do {', + ' do {', + ' alert(x);', + ' } while (--x);', + ' } while (x);', + '} else bar();', + ] +} + +issue_186_bracketize: { + beautify = { + beautify: false, + bracketize: true, + screw_ie8: true, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x=3;if(foo()){do{do{alert(x)}while(--x)}while(x)}else{bar()}' +} + +issue_186_bracketize_ie8: { + beautify = { + beautify: false, + bracketize: true, + screw_ie8: false, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: 'var x=3;if(foo()){do{do{alert(x)}while(--x)}while(x)}else{bar()}' +} + +issue_186_beautify_bracketize: { + beautify = { + beautify: true, + bracketize: true, + screw_ie8: true, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: [ + 'var x = 3;', + '', + 'if (foo()) {', + ' do {', + ' do {', + ' alert(x);', + ' } while (--x);', + ' } while (x);', + '} else {', + ' bar();', + '}', + ] +} + +issue_186_beautify_bracketize_ie8: { + beautify = { + beautify: true, + bracketize: true, + screw_ie8: false, + } + input: { + var x = 3; + if (foo()) + do + do + alert(x); + while (--x); + while (x); + else + bar(); + } + expect_exact: [ + 'var x = 3;', + '', + 'if (foo()) {', + ' do {', + ' do {', + ' alert(x);', + ' } while (--x);', + ' } while (x);', + '} else {', + ' bar();', + '}', + ] +} diff --git a/test/compress/max_line_len.js b/test/compress/max_line_len.js index b9e09178..7ad9ee0e 100644 --- a/test/compress/max_line_len.js +++ b/test/compress/max_line_len.js @@ -7,7 +7,13 @@ too_short: { return { c: 42, d: a(), e: "foo"}; } } - expect_exact: 'function f(a){\nreturn{\nc:42,\nd:a(),\ne:"foo"}}' + expect_exact: [ + 'function f(a){', + 'return{', + 'c:42,', + 'd:a(),', + 'e:"foo"}}', + ] expect_warnings: [ "WARN: Output exceeds 10 characters" ] @@ -22,7 +28,12 @@ just_enough: { return { c: 42, d: a(), e: "foo"}; } } - expect_exact: 'function f(a){\nreturn{c:42,\nd:a(),e:"foo"}\n}' + expect_exact: [ + 'function f(a){', + 'return{c:42,', + 'd:a(),e:"foo"}', + '}', + ] expect_warnings: [ ] } diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index f17ae206..9a0b5a46 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -32,6 +32,19 @@ negate_iife_2: { } } +negate_iife_2_side_effects: { + options = { + negate_iife: true, + side_effects: true, + } + input: { + (function(){ return {} })().x = 10; // should not transform this one + } + expect: { + (function(){ return {} })().x = 10; + } +} + negate_iife_3: { options = { negate_iife: true, @@ -45,6 +58,34 @@ negate_iife_3: { } } +negate_iife_3_evaluate: { + options = { + conditionals: true, + evaluate: true, + negate_iife: true, + } + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + console.log(true); + } +} + +negate_iife_3_side_effects: { + options = { + conditionals: true, + negate_iife: true, + side_effects: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + !function(){ return t }() ? console.log(false) : console.log(true); + } +} + negate_iife_3_off: { options = { negate_iife: false, @@ -58,6 +99,20 @@ negate_iife_3_off: { } } +negate_iife_3_off_evaluate: { + options = { + conditionals: true, + evaluate: true, + negate_iife: false, + } + input: { + (function(){ return true })() ? console.log(true) : console.log(false); + } + expect: { + console.log(true); + } +} + negate_iife_4: { options = { negate_iife: true, @@ -320,3 +375,35 @@ issue_1288: { }(0); } } + +issue_1288_side_effects: { + options = { + conditionals: true, + negate_iife: true, + side_effects: true, + } + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w; + x || function() { + x = {}; + }(); + y; + } +} diff --git a/test/compress/numbers.js b/test/compress/numbers.js index 8e32ad02..0b40bb9c 100644 --- a/test/compress/numbers.js +++ b/test/compress/numbers.js @@ -17,3 +17,139 @@ hex_numbers_in_parentheses_for_prototype_functions: { } expect_exact: "-2;(-2).toFixed(0);2;2..toFixed(0);.2;.2.toFixed(0);2e-8;2e-8.toFixed(0);0xde0b6b3a7640080;(0xde0b6b3a7640080).toFixed(0);" } + +comparisons: { + options = { + comparisons: true, + } + input: { + console.log( + ~x === 42, + x % n === 42 + ); + } + expect: { + console.log( + 42 == ~x, + x % n == 42 + ); + } +} + +evaluate_1: { + options = { + evaluate: true, + unsafe_math: false, + } + input: { + console.log( + x + 1 + 2, + x * 1 * 2, + +x + 1 + 2, + 1 + x + 2 + 3, + 1 | x | 2 | 3, + 1 + x-- + 2 + 3, + 1 + (x*y + 2) + 3, + 1 + (2 + x + 3), + 1 + (2 + ~x + 3), + -y + (2 + ~x + 3), + 1 & (2 & x & 3), + 1 + (2 + (x |= 0) + 3) + ); + } + expect: { + console.log( + x + 1 + 2, + 1 * x * 2, + +x + 1 + 2, + 1 + x + 2 + 3, + 3 | x, + 1 + x-- + 2 + 3, + x*y + 2 + 1 + 3, + 1 + (2 + x + 3), + 2 + ~x + 3 + 1, + -y + (2 + ~x + 3), + 0 & x, + 2 + (x |= 0) + 3 + 1 + ); + } +} + +evaluate_2: { + options = { + evaluate: true, + unsafe_math: true, + } + input: { + console.log( + x + 1 + 2, + x * 1 * 2, + +x + 1 + 2, + 1 + x + 2 + 3, + 1 | x | 2 | 3, + 1 + x-- + 2 + 3, + 1 + (x*y + 2) + 3, + 1 + (2 + x + 3), + 1 & (2 & x & 3), + 1 + (2 + (x |= 0) + 3) + ); + } + expect: { + console.log( + x + 1 + 2, + 2 * x, + 3 + +x, + 1 + x + 2 + 3, + 3 | x, + 6 + x--, + 6 + x*y, + 1 + (2 + x + 3), + 0 & x, + 6 + (x |= 0) + ); + } +} + +evaluate_3: { + options = { + evaluate: true, + unsafe: true, + unsafe_math: true, + } + input: { + console.log(1 + Number(x) + 2); + } + expect: { + console.log(3 + +x); + } +} + +evaluate_4: { + options = { + evaluate: true, + } + input: { + console.log( + 1+ +a, + +a+1, + 1+-a, + -a+1, + +a+ +b, + +a+-b, + -a+ +b, + -a+-b + ); + } + expect: { + console.log( + +a+1, + +a+1, + 1-a, + 1-a, + +a+ +b, + +a-b, + -a+ +b, + -a-b + ); + } +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index e38c317b..bc6c72d4 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -6,6 +6,7 @@ reduce_vars: { C : 0 }, reduce_vars : true, + toplevel : true, unused : true } input: { @@ -452,22 +453,26 @@ multi_def_2: { 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; + function f(){ + 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; + function f(){ + 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; + } } } @@ -477,12 +482,16 @@ use_before_var: { reduce_vars: true, } input: { - console.log(t); - var t = 1; + function f(){ + console.log(t); + var t = 1; + } } expect: { - console.log(t); - var t = 1; + function f(){ + console.log(t); + var t = 1; + } } } @@ -492,22 +501,20 @@ inner_var_if: { reduce_vars: true, } input: { - function f(){ - return 0; + function f(a){ + if (a) + var t = 1; + if (!t) + console.log(t); } - if (f()) - var t = 1; - if (!t) - console.log(t); } expect: { - function f(){ - return 0; + function f(a){ + if (a) + var t = 1; + if (!t) + console.log(t); } - if (f()) - var t = 1; - if (!t) - console.log(t); } } @@ -517,24 +524,22 @@ inner_var_label: { reduce_vars: true, } input: { - function f(){ - return 1; + function f(a){ + l: { + if (a) break l; + var t = 1; + } + console.log(t); } - l: { - if (f()) break l; - var t = 1; - } - console.log(t); } expect: { - function f(){ - return 1; + function f(a){ + l: { + if (a) break l; + var t = 1; + } + console.log(t); } - l: { - if (f()) break l; - var t = 1; - } - console.log(t); } } @@ -544,48 +549,805 @@ inner_var_for: { reduce_vars: true, } input: { - var a = 1; - x(a, b, d); - for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) { - var d = 4, e = 5; + function f() { + var a = 1; + x(a, b, d); + for (var b = 2, c = 3; x(a, b, c, d); x(a, b, c, d)) { + var d = 4, e = 5; + x(a, b, c, d, e); + } x(a, b, c, d, e); } - x(a, b, c, d, e) } expect: { - var a = 1; - x(1, b, d); - for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { - var d = 4, e = 5; + function f() { + var a = 1; + x(1, b, d); + for (var b = 2, c = 3; x(1, b, 3, d); x(1, b, 3, d)) { + var d = 4, e = 5; + x(1, b, 3, d, e); + } x(1, b, 3, d, e); } - x(1, b, 3, d, e); } } -inner_var_for_in: { +inner_var_for_in_1: { options = { evaluate: true, reduce_vars: true, } input: { - var a = 1, b = 2; - for (b in (function() { - return x(a, b, c); - })()) { - var c = 3, d = 4; + function f() { + var a = 1, b = 2; + for (b in (function() { + return x(a, b, c); + })()) { + var c = 3, d = 4; + x(a, b, c, d); + } x(a, b, c, d); } - x(a, b, c, d); } expect: { - var a = 1, b = 2; - for (b in (function() { - return x(1, b, c); - })()) { - var c = 3, d = 4; + function f() { + var a = 1, b = 2; + for (b in (function() { + return x(1, b, c); + })()) { + var c = 3, d = 4; + x(1, b, c, d); + } x(1, b, c, d); } - x(1, b, c, d); + } +} + +inner_var_for_in_2: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + for (var long_name in {}) + console.log(long_name); + } + } + expect: { + function f() { + for (var long_name in {}) + console.log(long_name); + } + } +} + +inner_var_catch: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); + } + } + expect: { + function f() { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); + } + } +} + +issue_1533_1: { + options = { + collapse_vars: true, + reduce_vars: true, + } + input: { + function f() { + var id = ""; + for (id in {break: "me"}) + console.log(id); + } + } + expect: { + function f() { + var id = ""; + for (id in {break: "me"}) + console.log(id); + } + } +} + +issue_1533_2: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + var id = ""; + for (var id in {break: "me"}) + console.log(id); + console.log(id); + } + } + expect: { + function f() { + var id = ""; + for (var id in {break: "me"}) + console.log(id); + console.log(id); + } + } +} + +toplevel_on: { + options = { + evaluate: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + var x = 3; + console.log(x); + } + expect: { + console.log(3); + } +} + +toplevel_off: { + options = { + evaluate: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + var x = 3; + console.log(x); + } + expect: { + var x = 3; + console.log(x); + } +} + +toplevel_on_loops_1: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + var x = 3; + do + (function() { + console.log("bar:", --x); + })(); + while (x); + } +} + +toplevel_off_loops_1: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:", --x); + } + var x = 3; + do + bar(); + while (x); + } +} + +toplevel_on_loops_2: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + for (;;) (function() { + console.log("bar:"); + })(); + } +} + +toplevel_off_loops_2: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } + expect: { + function bar() { + console.log("bar:"); + } + var x = 3; + do + bar(); + while (x); + } +} + +toplevel_on_loops_3: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:true, + unused: true, + } + input: { + var x = 3; + while (x) bar(); + } + expect: { + for (;;) bar(); + } +} + +toplevel_off_loops_3: { + options = { + evaluate: true, + loops: true, + reduce_vars: true, + toplevel:false, + unused: true, + } + input: { + var x = 3; + while (x) bar(); + } + expect: { + var x = 3; + for (;x;) bar(); + } +} + +defun_reference: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } + expect: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } +} + +defun_inline_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g(2) + h(); + function g(b) { + return b; + } + function h() { + return h(); + } + } + } + expect: { + function f() { + return function(b) { + return b; + }(2) + h(); + function h() { + return h(); + } + } + } +} + +defun_inline_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g(b) { + return b; + } + function h() { + return h(); + } + return g(2) + h(); + } + } + expect: { + function f() { + function h() { + return h(); + } + return function(b) { + return b; + }(2) + h(); + } + } +} + +defun_inline_3: { + options = { + evaluate: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + function f() { + return g(2); + function g(b) { + return b; + } + } + } + expect: { + function f() { + return 2; + } + } +} + +defun_call: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g() + h(1) - h(g(), 2, 3); + function g() { + return 4; + } + function h(a) { + return a; + } + } + } + expect: { + function f() { + return 4 + h(1) - h(4); + function h(a) { + return a; + } + } + } +} + +defun_redefine: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g() { + return 1; + } + function h() { + return 2; + } + g = function() { + return 3; + }; + return g() + h(); + } + } + expect: { + function f() { + function g() { + return 1; + } + g = function() { + return 3; + }; + return g() + 2; + } + } +} + +func_inline: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var g = function() { + return 1; + }; + console.log(g() + h()); + var h = function() { + return 2; + }; + } + } + expect: { + function f() { + console.log(1 + h()); + var h = function() { + return 2; + }; + } + } +} + +func_modified: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f(a) { + function a() { return 1; } + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return a() + b() + c(); + } + } + expect: { + function f(a) { + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return 1 + 2 + c(); + } + } +} + +defun_label: { + options = { + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + !function() { + function f(a) { + L: { + if (a) break L; + return 1; + } + } + console.log(f(2)); + }(); + } + expect: { + !function() { + console.log(function(a) { + L: { + if (a) break L; + return 1; + } + }(2)); + }(); + } +} + +double_reference: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var g = function g() { + g(); + }; + g(); + } + } + expect: { + function f() { + (function g() { + g(); + })(); + } + } +} + +iife_arguments_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function(x) { + console.log(x() === arguments[0]); + })(function f() { + return f; + }); + } + expect: { + (function(x) { + console.log(x() === arguments[0]); + })(function f() { + return f; + }); + } +} + +iife_arguments_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function() { + var x = function f() { + return f; + }; + console.log(x() === arguments[0]); + })(); + } + expect: { + (function() { + console.log(function f() { + return f; + }() === arguments[0]); + })(); + } +} + +iife_eval_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function(x) { + console.log(x() === eval("x")); + })(function f() { + return f; + }); + } + expect: { + (function(x) { + console.log(x() === eval("x")); + })(function f() { + return f; + }); + } +} + +iife_eval_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function() { + var x = function f() { + return f; + }; + console.log(x() === eval("x")); + })(); + } + expect: { + (function() { + var x = function f() { + return f; + }; + console.log(x() === eval("x")); + })(); + } +} + +iife_func_side_effects: { + options = { + reduce_vars: true, + unused: true, + } + input: { + (function(a, b, c) { + return b(); + })(x(), function() { + return y(); + }, z()); + } + expect: { + (function(a, b, c) { + return function() { + return y(); + }(); + })(x(), 0, z()); + } +} + +issue_1595_1: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + (function f(a) { + return f(a + 1); + })(2); + } + expect: { + (function f(a) { + return f(a + 1); + })(2); + } +} + +issue_1595_2: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + (function f(a) { + return g(a + 1); + })(2); + } + expect: { + (function(a) { + return g(a + 1); + })(2); + } +} + +issue_1595_3: { + options = { + evaluate: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + (function f(a) { + return g(a + 1); + })(2); + } + expect: { + (function(a) { + return g(3); + })(); + } +} + +issue_1595_4: { + options = { + evaluate: true, + reduce_vars: true, + unused: true, + } + input: { + (function iife(a, b, c) { + console.log(a, b, c); + if (a) iife(a - 1, b, c); + })(3, 4, 5); + } + expect: { + (function iife(a, b, c) { + console.log(a, b, c); + if (a) iife(a - 1, b, c); + })(3, 4, 5); + } +} + +issue_1606: { + options = { + evaluate: true, + hoist_vars: true, + reduce_vars: true, + } + input: { + function f() { + var a; + function g(){}; + var b = 2; + x(b); + } + } + expect: { + function f() { + var a, b; + function g(){}; + b = 2; + x(b); + } } } diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js index 31c448fd..4fbb95c8 100644 --- a/test/compress/screw-ie8.js +++ b/test/compress/screw-ie8.js @@ -17,6 +17,26 @@ dont_screw: { expect_exact: 'f("\\x0B");'; } +do_screw_constants: { + options = { + screw_ie8: true, + } + input: { + f(undefined, Infinity); + } + expect_exact: "f(void 0,1/0);" +} + +dont_screw_constants: { + options = { + screw_ie8: false, + } + input: { + f(undefined, Infinity); + } + expect_exact: "f(undefined,Infinity);" +} + do_screw_try_catch: { options = { screw_ie8: true }; mangle = { screw_ie8: true }; @@ -46,8 +66,6 @@ do_screw_try_catch: { } dont_screw_try_catch: { - // This test is known to generate incorrect code for screw_ie8=false. - // Update expected result in the event this bug is ever fixed. options = { screw_ie8: false }; mangle = { screw_ie8: false }; beautify = { screw_ie8: false }; @@ -64,11 +82,11 @@ dont_screw_try_catch: { } expect: { bad = function(n){ - return function(n){ + return function(t){ try{ - t() - } catch(t) { - n(t) + n() + } catch(n) { + t(n) } } }; @@ -104,8 +122,6 @@ do_screw_try_catch_undefined: { } dont_screw_try_catch_undefined: { - // This test is known to generate incorrect code for screw_ie8=false. - // Update expected result in the event this bug is ever fixed. options = { screw_ie8: false }; mangle = { screw_ie8: false }; beautify = { screw_ie8: false }; @@ -121,14 +137,84 @@ dont_screw_try_catch_undefined: { }; } expect: { - function a(o){ + function a(n){ try{ throw "Stuff" - } catch (n) { - console.log("caught: "+n) + } catch (undefined) { + console.log("caught: " + undefined) } - console.log("undefined is " + n); - return o === n + console.log("undefined is " + undefined); + return n === undefined } } } + +reduce_vars: { + options = { + evaluate: true, + reduce_vars: true, + screw_ie8: false, + unused: true, + } + mangle = { + screw_ie8: false, + } + input: { + function f() { + var a; + try { + x(); + } catch (a) { + y(); + } + alert(a); + } + } + expect: { + function f() { + var t; + try { + x(); + } catch (t) { + y(); + } + alert(t); + } + } +} + +issue_1586_1: { + options = { + screw_ie8: false, + } + mangle = { + screw_ie8: false, + } + input: { + function f() { + try { + } catch (err) { + console.log(err.message); + } + } + } + expect_exact: "function f(){try{}catch(c){console.log(c.message)}}" +} + +issue_1586_2: { + options = { + screw_ie8: true, + } + mangle = { + screw_ie8: true, + } + input: { + function f() { + try { + } catch (err) { + console.log(err.message); + } + } + } + expect_exact: "function f(){try{}catch(c){console.log(c.message)}}" +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index d93f5237..41cfc726 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -248,6 +248,39 @@ iife: { } expect: { x = 42, function a() {}(), function b() {}(), function c() {}(), - function d() {}(), function e() {}(), function f() {}(), function g() {}() + function d() {}(), function e() {}(), function f() {}(), function g() {}(); + } +} + +unsafe_undefined: { + options = { + conditionals: true, + if_return: true, + sequences: true, + side_effects: true, + unsafe: true, + } + input: { + function f(undefined) { + if (a) + return b; + if (c) + return d; + } + function g(undefined) { + if (a) + return b; + if (c) + return d; + e(); + } + } + expect: { + function f(undefined) { + return a ? b : c ? d : undefined; + } + function g(undefined) { + return a ? b : c ? d : void e(); + } } } diff --git a/test/compress/transform.js b/test/compress/transform.js new file mode 100644 index 00000000..7b616e40 --- /dev/null +++ b/test/compress/transform.js @@ -0,0 +1,129 @@ +booleans_evaluate: { + options = { + booleans: true, + evaluate: true, + } + input: { + console.log(typeof void 0 != "undefined"); + console.log(1 == 1, 1 === 1) + console.log(1 != 1, 1 !== 1) + } + expect: { + console.log(!1); + console.log(!0, !0); + console.log(!1, !1); + } +} + +booleans_global_defs: { + options = { + booleans: true, + evaluate: true, + global_defs: { + A: true, + }, + } + input: { + console.log(A == 1); + } + expect: { + console.log(!0); + } +} + +condition_evaluate: { + options = { + booleans: true, + dead_code: false, + evaluate: true, + loops: false, + } + input: { + while (1 === 2); + for (; 1 == true;); + if (void 0 == null); + } + expect: { + while (!1); + for (; !0;); + if (!0); + } +} + +if_else_empty: { + options = { + conditionals: true, + } + input: { + if ({} ? a : b); else {} + } + expect: { + !{} ? b : a; + } +} + +label_if_break: { + options = { + conditionals: true, + dead_code: true, + evaluate: true, + } + input: { + L: if (true) { + a; + break L; + } + } + expect: { + a; + } +} + +while_if_break: { + options = { + conditionals: true, + loops: true, + sequences: true, + } + input: { + while (a) { + if (b) if(c) d; + if (e) break; + } + } + expect: { + for(; a && (b && c && d, !e);); + } +} + +if_return: { + options = { + booleans: true, + conditionals: true, + if_return: true, + sequences: true, + } + input: { + function f(w, x, y, z) { + if (x) return; + if (w) { + if (y) return; + } else if (z) return; + if (x == y) return true; + + if (x) w(); + if (y) z(); + return true; + } + } + expect: { + function f(w, x, y, z) { + if (!x) { + if (w) { + if (y) return; + } else if (z) return; + return x == y || (x && w(), y && z(), !0); + } + } + } +} diff --git a/test/input/invalid/loop-no-body.js b/test/input/invalid/loop-no-body.js new file mode 100644 index 00000000..07b27428 --- /dev/null +++ b/test/input/invalid/loop-no-body.js @@ -0,0 +1 @@ +for (var i = 0; i < 1; i++) diff --git a/test/mocha/cli.js b/test/mocha/cli.js index c07eeee7..2b44c901 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -82,7 +82,7 @@ describe("bin/uglifyjs", function () { }); }); it("Should work with --keep-fnames (mangle & compress)", function (done) { - var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c'; + var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c unused=false'; exec(command, function (err, stdout) { if (err) throw err; @@ -152,7 +152,7 @@ describe("bin/uglifyjs", function () { }); }); it("Should process inline source map", function(done) { - var command = uglifyjscmd + ' test/input/issue-520/input.js -cm toplevel --in-source-map inline --source-map-inline'; + var command = uglifyjscmd + ' test/input/issue-520/input.js -mc toplevel --in-source-map inline --source-map-inline'; exec(command, function (err, stdout) { if (err) throw err; @@ -238,4 +238,17 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should fail with a missing loop body", function(done) { + var command = uglifyjscmd + ' test/input/invalid/loop-no-body.js'; + + exec(command, function (err, stdout, stderr) { + assert.ok(err); + var lines = stderr.split(/\n/); + assert.strictEqual(lines[0], "Parse error at test/input/invalid/loop-no-body.js:2,0"); + assert.strictEqual(lines[1], "for (var i = 0; i < 1; i++) "); + assert.strictEqual(lines[2], " ^"); + assert.strictEqual(lines[3], "SyntaxError: Unexpected token: eof (undefined)"); + done(); + }); + }); }); diff --git a/test/mocha/comment-filter.js b/test/mocha/comment-filter.js index 01580c87..9474e732 100644 --- a/test/mocha/comment-filter.js +++ b/test/mocha/comment-filter.js @@ -72,4 +72,12 @@ describe("comment filters", function() { assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); }); + + it("Should handle shebang and preamble correctly", function() { + var code = UglifyJS.minify("#!/usr/bin/node\nvar x = 10;", { + fromString: true, + output: { preamble: "/* Build */" } + }).code; + assert.strictEqual(code, "#!/usr/bin/node\n/* Build */\nvar x=10;"); + }) }); diff --git a/test/mocha/glob.js b/test/mocha/glob.js index c2fc9464..30313656 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -3,17 +3,13 @@ var assert = require("assert"); describe("minify() with input file globs", function() { it("minify() with one input file glob string.", function() { - var result = Uglify.minify("test/input/issue-1242/foo.*", { - compress: { collapse_vars: true } - }); + var result = Uglify.minify("test/input/issue-1242/foo.*"); assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);'); }); it("minify() with an array of one input file glob.", function() { var result = Uglify.minify([ "test/input/issue-1242/b*.es5", - ], { - compress: { collapse_vars: true } - }); + ]); assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}'); }); it("minify() with an array of multiple input file globs.", function() { @@ -21,8 +17,8 @@ describe("minify() with input file globs", function() { "test/input/issue-1242/???.es5", "test/input/issue-1242/*.js", ], { - compress: { collapse_vars: true } + compress: { toplevel: true } }); - assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}function foo(n){print("Foo:",2*n)}var print=console.log.bind(console);print("qux",bar(3),baz(12)),foo(11);'); + assert.strictEqual(result.code, 'var print=console.log.bind(console);print("qux",function(n){return 3*n}(3),function(n){return n/2}(12)),function(n){print("Foo:",2*n)}(11);'); }); }); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 51c46b28..a4587cb7 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -78,6 +78,7 @@ describe("minify", function() { }); it("Should process inline source map", function() { var code = Uglify.minify("./test/input/issue-520/input.js", { + compress: { toplevel: true }, inSourceMap: "inline", sourceMapInline: true }).code + "\n"; diff --git a/test/mocha/release.js b/test/mocha/release.js new file mode 100644 index 00000000..3b2d9a72 --- /dev/null +++ b/test/mocha/release.js @@ -0,0 +1,54 @@ +var assert = require("assert"); +var spawn = require("child_process").spawn; + +if (!process.env.UGLIFYJS_TEST_ALL) return; + +function run(command, args, done) { + var id = setInterval(function() { + process.stdout.write("\0"); + }, 5 * 60 * 1000); + spawn(command, args, { + stdio: "ignore" + }).on("exit", function(code) { + clearInterval(id); + assert.strictEqual(code, 0); + done(); + }); +} + +describe("test/benchmark.js", function() { + this.timeout(5 * 60 * 1000); + [ + "-b", + "-b bracketize", + "-m", + "-mc passes=3", + "-mc passes=3,toplevel", + "-mc passes=3,unsafe", + "-mc keep_fargs=false,passes=3", + "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", + ].forEach(function(options) { + it("Should pass with options " + options, function(done) { + var args = options.split(/ /); + args.unshift("test/benchmark.js"); + run(process.argv[0], args, done); + }); + }); +}); + +describe("test/jetstream.js", function() { + this.timeout(20 * 60 * 1000); + it("Should install phantomjs-prebuilt", function(done) { + run("npm", ["install", "phantomjs-prebuilt@2.1.14"], done); + }); + [ + "-mc warnings=false", + "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto,warnings=false", + ].forEach(function(options) { + it("Should pass with options " + options, function(done) { + var args = options.split(/ /); + args.unshift("test/jetstream.js"); + run(process.argv[0], args, done); + }); + }); +}); diff --git a/test/run-tests.js b/test/run-tests.js index 15a12c6b..898bb793 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -6,6 +6,7 @@ var U = require("../tools/node"); var path = require("path"); var fs = require("fs"); var assert = require("assert"); +var vm = require("vm"); var tests_dir = path.dirname(module.filename); var failures = 0; @@ -165,6 +166,51 @@ function run_compress_tests() { failed_files[file] = 1; } } + if (test.expect_stdout) { + try { + var stdout = run_code(input_code); + if (test.expect_stdout === true) { + test.expect_stdout = stdout; + } + if (test.expect_stdout != stdout) { + log("!!! Invalid input or expected stdout\n---INPUT---\n{input}\n---EXPECTED STDOUT---\n{expected}\n---ACTUAL STDOUT---\n{actual}\n\n", { + input: input_code, + expected: test.expect_stdout, + actual: stdout, + }); + failures++; + failed_files[file] = 1; + } else { + try { + stdout = run_code(output); + if (test.expect_stdout != stdout) { + log("!!! failed\n---INPUT---\n{input}\n---EXPECTED STDOUT---\n{expected}\n---ACTUAL STDOUT---\n{actual}\n\n", { + input: input_code, + expected: test.expect_stdout, + actual: stdout, + }); + failures++; + failed_files[file] = 1; + } + } catch (ex) { + log("!!! Execution of output failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n--ERROR--\n{error}\n\n", { + input: input_code, + output: output, + error: ex.toString(), + }); + failures++; + failed_files[file] = 1; + } + } + } catch (ex) { + log("!!! Execution of input failed\n---INPUT---\n{input}\n--ERROR--\n{error}\n\n", { + input: input_code, + error: ex.toString(), + }); + failures++; + failed_files[file] = 1; + } + } } } var tests = parse_test(path.resolve(dir, file)); @@ -214,6 +260,23 @@ function parse_test(file) { })); } + function read_string(stat) { + if (stat.TYPE == "SimpleStatement") { + var body = stat.body; + switch(body.TYPE) { + case "String": + return body.value; + case "Array": + return body.elements.map(function(element) { + if (element.TYPE !== "String") + throw new Error("Should be array of strings"); + return element.value; + }).join("\n"); + } + } + throw new Error("Should be string or array of strings"); + } + function get_one_test(name, block) { var test = { name: name, options: {} }; var tw = new U.TreeWalker(function(node, descend){ @@ -226,12 +289,13 @@ function parse_test(file) { return true; } if (node instanceof U.AST_LabeledStatement) { + var label = node.label; assert.ok( - ["input", "expect", "expect_exact", "expect_warnings"].indexOf(node.label.name) >= 0, + ["input", "expect", "expect_exact", "expect_warnings", "expect_stdout"].indexOf(label.name) >= 0, tmpl("Unsupported label {name} [{line},{col}]", { - name: node.label.name, - line: node.label.start.line, - col: node.label.start.col + name: label.name, + line: label.start.line, + col: label.start.col }) ); var stat = node.body; @@ -239,15 +303,16 @@ function parse_test(file) { if (stat.body.length == 1) stat = stat.body[0]; else if (stat.body.length == 0) stat = new U.AST_EmptyStatement(); } - if (node.label.name === "expect_exact") { - if (!(stat.TYPE === "SimpleStatement" && stat.body.TYPE === "String")) { - throw new Error( - "The value of the expect_exact clause should be a string, " + - "like `expect_exact: \"some.exact.javascript;\"`"); + if (label.name == "expect_exact") { + test[label.name] = read_string(stat); + } else if (label.name == "expect_stdout") { + if (stat.TYPE == "SimpleStatement" && stat.body instanceof U.AST_Boolean) { + test[label.name] = stat.body.value; + } else { + test[label.name] = read_string(stat) + "\n"; } - test[node.label.name] = stat.body.start.value } else { - test[node.label.name] = stat; + test[label.name] = stat; } return true; } @@ -269,3 +334,17 @@ function evaluate(code) { code = make_code(code, { beautify: true }); return new Function("return(" + code + ")")(); } + +function run_code(code) { + var stdout = ""; + var original_write = process.stdout.write; + process.stdout.write = function(chunk) { + stdout += chunk; + }; + try { + new vm.Script(code).runInNewContext({ console: console }, { timeout: 5000 }); + return stdout; + } finally { + process.stdout.write = original_write; + } +}