diff --git a/README.md b/README.md index 368492c342a..96014f4c17f 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)` @@ -423,6 +424,9 @@ to set `true`; it's effectively a shortcut for `foo=true`). such as `console.info` and/or retain side effects from function arguments after dropping the function call then use `pure_funcs` instead. +- `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 367d66e2cb0..e39a4b4bf27 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 64352ae04ea..cd32ead769c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -1104,9 +1104,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, { diff --git a/lib/compress.js b/lib/compress.js index 253d5699c75..11a1ff1cc03 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) { ecma : 5, drop_console : 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,8 +186,45 @@ 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 ie8 = !compressor.option("screw_ie8"); var safe_ids = []; push(); var suppressor = new TreeWalker(function(node) { @@ -189,7 +234,7 @@ merge(Compressor.prototype, { d.fixed = false; } }); - var tw = new TreeWalker(function(node){ + var tw = new TreeWalker(function(node, descend){ if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; @@ -204,6 +249,9 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (ie8 && node instanceof AST_SymbolCatch) { + node.definition().fixed = false; + } if (node instanceof AST_VarDef) { if (node.name instanceof AST_Destructuring) { node.name.walk(suppressor); @@ -262,6 +310,12 @@ merge(Compressor.prototype, { pop(); return true; } + if (node instanceof AST_Catch) { + push(); + descend(); + pop(); + return true; + } } }); this.walk(tw); @@ -494,11 +548,10 @@ 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) { - if (node === ref) { - var parent = ctt.parent(); - if (!(parent instanceof AST_ForIn && parent.init === node)) { - return replace_var(node, parent, true); - } + if (node === ref + && !ctt.find_parent(AST_Destructuring) + && !ctt.find_parent(AST_ForIn)) { + return replace_var(node, ctt.parent(), true); } }); stat.transform(ctt); @@ -529,6 +582,7 @@ merge(Compressor.prototype, { if (unwind) return node; var parent = tt.parent(); if (node instanceof AST_Lambda + || node instanceof AST_Destructuring || node instanceof AST_Try || node instanceof AST_With || node instanceof AST_Case @@ -568,10 +622,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; @@ -744,7 +795,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; @@ -777,7 +828,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; @@ -1034,6 +1085,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 @@ -1064,6 +1119,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, function(){ return false }); @@ -1092,7 +1175,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; } @@ -2117,7 +2200,14 @@ 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) { + 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, ' '); @@ -2441,8 +2531,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); } @@ -2623,12 +2713,13 @@ merge(Compressor.prototype, { }); OPT(AST_Call, function(self, compressor){ + var exp = self.expression; 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) { @@ -2638,7 +2729,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": @@ -2817,17 +2907,24 @@ merge(Compressor.prototype, { return best_of(self, node); } } - if (compressor.option("side_effects")) { - if (self.expression instanceof AST_Function - && !self.expression.is_generator - && 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 && !self.expression.is_generator) { + 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; } @@ -2838,12 +2935,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)) { @@ -2875,23 +2966,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 (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.car.has_side_effects(compressor) - && self.car.equivalent_to(self.cdr)) { - return self.car; - } } - if (self.cdr instanceof AST_Undefined) { + if (is_undefined(self.cdr)) { return make_node(AST_UnaryPrefix, self, { operator : "void", expression : self.car @@ -2930,7 +3039,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()) { @@ -3003,8 +3112,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; @@ -3020,7 +3135,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)) { @@ -3055,6 +3170,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); } @@ -3192,7 +3308,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 == "+" @@ -3200,7 +3319,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 @@ -3208,6 +3327,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 == "+" @@ -3216,13 +3336,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) @@ -3236,7 +3357,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 @@ -3245,6 +3366,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 @@ -3277,11 +3514,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": @@ -3324,11 +3563,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; @@ -3631,7 +3872,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 1e0ff175149..cb2b62bba3f 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) { @@ -95,7 +86,7 @@ function OutputStream(options) { options.shorthand = options.ecma > 5; // 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)) { @@ -107,12 +98,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") { @@ -444,10 +435,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, @@ -572,6 +559,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 @@ -596,10 +594,6 @@ function OutputStream(options) { output.space(); } } - else if (output.pos() === 0 && c.type == "comment5" && output.option("shebang")) { - output.print("#!" + c.value + "\n"); - output.indent(); - } }); } }); diff --git a/lib/parse.js b/lib/parse.js index 2164e52a98b..4e671da451a 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -700,6 +700,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(); @@ -742,13 +747,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(ch)) return read_word(); - if (shebang) { - if (S.pos == 0 && looking_at("#!")) { - forward(2); - skip_line_comment("comment5"); - continue; - } - } break; } parse_error("Unexpected character '" + ch + "'"); diff --git a/lib/scope.js b/lib/scope.js index 42afe87cabb..2edea20e739 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -209,14 +209,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ (node.scope = defun.parent_scope).def_function(node, in_export, in_block); } else if (node instanceof AST_SymbolVar - || node instanceof AST_SymbolConst - || node instanceof AST_SymbolLet) { + || node instanceof AST_SymbolLet + || node instanceof AST_SymbolConst) { var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export, in_block); def.destructuring = in_destructuring; - def.init = tw.parent().value; } else if (node instanceof AST_SymbolCatch) { - (options.screw_ie8 ? scope : defun).def_variable(node, in_export, in_block); + scope.def_variable(node, in_export, in_block); } else if (node instanceof AST_LabelRef) { var sym = labels.get(node.name); @@ -274,6 +273,23 @@ 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 scope = node.thedef.scope.parent_scope; + var def = scope.find_variable(name) || self.globals.get(name) || scope.def_variable(node); + node.thedef.references.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/package.json b/package.json index f152a51e68a..31aaf9515d1 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.5", + "version": "2.8.7", "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/compress/collapse_vars.js b/test/compress/collapse_vars.js index c8fa046051b..6d9961ff463 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1343,3 +1343,81 @@ issue_1537_for_of: { for (k of {prop: 'val'}); } } + +issue_1537_destructuring_1: { + options = { + collapse_vars: true, + } + input: { + var x = 1, y = 2; + [x] = [y]; + } + expect: { + var x = 1; + [x] = [2]; + } +} + +issue_1537_destructuring_2: { + options = { + collapse_vars: true, + } + input: { + var x = foo(); + [x] = [1]; + } + expect: { + var x = foo(); + [x] = [1]; + } +} + +issue_1537_destructuring_3: { + options = { + collapse_vars: true, + } + input: { + var x = Math.random(); + ({p: x = 9} = {v: 1}); + } + expect: { + var x = Math.random(); + ({p: x = 9} = {v: 1}); + } +} + +issue_1537_destructuring_for_in: { + options = { + collapse_vars: true, + } + input: { + var x = 1, y = 2; + (function() { + for ([[x], y] in a); + })(); + } + expect: { + var x = 1, y = 2; + (function() { + for ([[x], y] in a); + })(); + } +} + +issue_1537_destructuring_for_of: { + options = { + collapse_vars: true, + } + input: { + var x = 1, y = 2; + (function() { + for ([[x], y] of a); + })(); + } + expect: { + var x = 1, y = 2; + (function() { + for ([[x], y] of a); + })(); + } +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index d8dd0ea4e7d..fd1c2fb3368 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -713,7 +713,7 @@ iife: { } expect: { function f() { - ~function() {}(b); + b; } } } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index ef0c4c78df4..5adbc9ea11b 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -734,9 +734,7 @@ call_args: { expect: { const a = 1; console.log(1); - +function(a) { - return 1; - }(1); + +(1, 1); } } @@ -757,9 +755,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 d3d99b57a18..a1a515a13d0 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 a2565872d0c..304a71ac0ce 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-368.js b/test/compress/issue-368.js new file mode 100644 index 00000000000..5960aa64ab4 --- /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 00000000000..dd3f3f2196a --- /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/negate-iife.js b/test/compress/negate-iife.js index f17ae206ce9..9a0b5a46867 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 8e32ad02a75..0b40bb9c88d 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 557631bd19a..70e915d3283 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -605,6 +605,29 @@ inner_var_for_in_2: { } } +inner_var_catch: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); + } + expect: { + try { + a(); + } catch (e) { + var b = 1; + } + console.log(b); + } +} + issue_1533_1: { options = { collapse_vars: true, diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js index 31c448fd091..36eb4d3a1da 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,48 @@ 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 " + 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(); } - console.log("undefined is " + n); - return o === n + alert(t); } } } diff --git a/test/compress/sequences.js b/test/compress/sequences.js index d93f5237259..41cfc726159 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/mocha/comment-filter.js b/test/mocha/comment-filter.js index 01580c87dbf..9474e73208b 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;"); + }) });