diff --git a/README.md b/README.md index c2434547..67324dbd 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `drop_console` -- default `false`. Pass `true` to discard calls to `console.*` functions. -- `keep_fargs` -- default `false`. Pass `true` to prevent the +- `keep_fargs` -- default `true`. Prevents the compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. @@ -372,7 +372,6 @@ when this flag is on: - `void 0` → `undefined` (if there is a variable named "undefined" in scope; we do it because the variable name will be mangled, typically reduced to a single character) -- discards unused function arguments (affects `function.length`) ### Conditional compilation @@ -784,7 +783,7 @@ The `source_map_options` (optional) can contain the following properties: came from. It can be simply a string in JSON, or a JSON object containing the original source map. - [acorn]: https://github.com/marijnh/acorn + [acorn]: https://github.com/ternjs/acorn [source-map]: https://github.com/mozilla/source-map [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit [codegen]: http://lisperator.net/uglifyjs/codegen diff --git a/bin/uglifyjs b/bin/uglifyjs index ca75f159..f7f22215 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -73,6 +73,7 @@ You need to pass an argument to this option to specify the name that your module .describe("mangle-regex", "Only mangle property names matching the regex") .describe("name-cache", "File to hold mangled names mappings") .describe("pure-funcs", "List of functions that can be safely removed if their return value is not used") + .describe("dump-spidermonkey-ast", "Dump SpiderMonkey AST to stdout.") .alias("p", "prefix") .alias("o", "output") @@ -117,6 +118,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("stats") .boolean("acorn") .boolean("spidermonkey") + .boolean("dump-spidermonkey-ast") .boolean("lint") .boolean("V") .boolean("version") @@ -407,14 +409,17 @@ async.eachLimit(files, 1, function (file, cb) { writeNameCache("props", cache); })(); + var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint var TL_CACHE = readNameCache("vars"); - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (ARGS.lint) { - TOPLEVEL.scope_warnings(); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (ARGS.lint) { + TOPLEVEL.scope_warnings(); + } + }); + } if (COMPRESS) { time_it("squeeze", function(){ @@ -422,12 +427,14 @@ async.eachLimit(files, 1, function (file, cb) { }); } - time_it("scope", function(){ - TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); - if (MANGLE && !TL_CACHE) { - TOPLEVEL.compute_char_frequency(MANGLE); - } - }); + if (SCOPE_IS_NEEDED) { + time_it("scope", function(){ + TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8, cache: TL_CACHE }); + if (MANGLE && !TL_CACHE) { + TOPLEVEL.compute_char_frequency(MANGLE); + } + }); + } if (MANGLE) time_it("mangle", function(){ MANGLE.cache = TL_CACHE; @@ -444,26 +451,30 @@ async.eachLimit(files, 1, function (file, cb) { } } - time_it("generate", function(){ - TOPLEVEL.print(output); - }); - - output = output.get(); - - if (SOURCE_MAP) { - fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); - var source_map_url = ARGS.source_map_url || ( - P_RELATIVE - ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map) - : ARGS.source_map - ); - output += "\n//# sourceMappingURL=" + source_map_url; - } - - if (OUTPUT_FILE) { - fs.writeFileSync(OUTPUT_FILE, output, "utf8"); + if (ARGS.dump_spidermonkey_ast) { + print(JSON.stringify(TOPLEVEL.to_mozilla_ast(), null, 2)); } else { - print(output); + time_it("generate", function(){ + TOPLEVEL.print(output); + }); + + output = output.get(); + + if (SOURCE_MAP) { + fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8"); + var source_map_url = ARGS.source_map_url || ( + P_RELATIVE + ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map) + : ARGS.source_map + ); + output += "\n//# sourceMappingURL=" + source_map_url; + } + + if (OUTPUT_FILE) { + fs.writeFileSync(OUTPUT_FILE, output, "utf8"); + } else { + print(output); + } } if (ARGS.stats) { diff --git a/lib/ast.js b/lib/ast.js index c315c4a1..0541dc89 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -85,7 +85,7 @@ function DEFNODE(type, props, methods, base) { return ctor; }; -var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file", { +var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos nlb comments_before file raw", { }, null); var AST_Node = DEFNODE("Node", "start end", { @@ -1159,27 +1159,36 @@ var AST_True = DEFNODE("True", null, { function TreeWalker(callback) { this.visit = callback; this.stack = []; + this.directives = Object.create(null); }; TreeWalker.prototype = { _visit: function(node, descend) { - this.stack.push(node); + this.push(node); var ret = this.visit(node, descend ? function(){ descend.call(node); } : noop); if (!ret && descend) { descend.call(node); } - this.stack.pop(); + this.pop(node); return ret; }, parent: function(n) { return this.stack[this.stack.length - 2 - (n || 0)]; }, 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; + } this.stack.push(node); }, - pop: function() { - return this.stack.pop(); + pop: function(node) { + this.stack.pop(); + if (node instanceof AST_Lambda) { + this.directives = Object.getPrototypeOf(this.directives); + } }, self: function() { return this.stack[this.stack.length - 1]; @@ -1192,7 +1201,16 @@ TreeWalker.prototype = { } }, has_directive: function(type) { - return this.find_parent(AST_Scope).has_directive(type); + var dir = this.directives[type]; + if (dir) return dir; + var node = this.stack[this.stack.length - 1]; + if (node instanceof AST_Scope && node.body) { + 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; + } + } }, in_boolean_context: function() { var stack = this.stack; diff --git a/lib/compress.js b/lib/compress.js index c990712e..52fbb74d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -199,7 +199,7 @@ merge(Compressor.prototype, { }; function tighten_body(statements, compressor) { - var CHANGED; + var CHANGED, max_iter = 10; do { CHANGED = false; if (compressor.option("angular")) { @@ -218,7 +218,7 @@ merge(Compressor.prototype, { if (compressor.option("join_vars")) { statements = join_consecutive_vars(statements, compressor); } - } while (CHANGED); + } while (CHANGED && max_iter-- > 0); if (compressor.option("negate_iife")) { negate_iifes(statements, compressor); @@ -392,7 +392,12 @@ merge(Compressor.prototype, { continue loop; } //--- - if (ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement + // XXX: what was the intention of this case? + // if sequences is not enabled, this can lead to an endless loop (issue #866). + // however, with sequences on this helps producing slightly better output for + // the example code. + if (compressor.option("sequences") + && ret.length == 1 && in_lambda && ret[0] instanceof AST_SimpleStatement && (!stat.alternative || stat.alternative instanceof AST_SimpleStatement)) { CHANGED = true; ret.push(make_node(AST_Return, ret[0], { @@ -729,6 +734,32 @@ merge(Compressor.prototype, { return [ this ]; } }); + AST_Node.DEFMETHOD("is_constant", function(compressor){ + // Accomodate when compress option evaluate=false + // as well as the common constant expressions !0 and !1 + return this instanceof AST_Constant + || (this instanceof AST_UnaryPrefix && this.operator == "!" + && this.expression instanceof AST_Constant) + || this.evaluate(compressor).length > 1; + }); + // Obtain the constant value of an expression already known to be constant. + // Result only valid iff this.is_constant(compressor) is true. + AST_Node.DEFMETHOD("constant_value", function(compressor){ + // Accomodate when option evaluate=false. + if (this instanceof AST_Constant) return this.value; + // Accomodate the common constant expressions !0 and !1 when option evaluate=false. + if (this instanceof AST_UnaryPrefix + && this.operator == "!" + && this.expression instanceof AST_Constant) { + return !this.expression.value; + } + var result = this.evaluate(compressor) + if (result.length > 1) { + return result[1]; + } + // should never be reached + return undefined; + }); def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); @@ -1006,7 +1037,7 @@ merge(Compressor.prototype, { /* -----[ optimizers ]----- */ OPT(AST_Directive, function(self, compressor){ - if (self.scope.has_directive(self.value) !== self.scope) { + if (compressor.has_directive(self.value) === "up") { return make_node(AST_EmptyStatement, self); } return self; @@ -2472,32 +2503,48 @@ merge(Compressor.prototype, { alternative: alternative }); } - // x=y?1:1 --> x=1 - if (consequent instanceof AST_Constant - && alternative instanceof AST_Constant + // y?1:1 --> 1 + if (consequent.is_constant(compressor) + && alternative.is_constant(compressor) && consequent.equivalent_to(alternative)) { + var consequent_value = consequent.constant_value(); if (self.condition.has_side_effects(compressor)) { - return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent.value, self)]); + return AST_Seq.from_array([self.condition, make_node_from_constant(compressor, consequent_value, self)]); } else { - return make_node_from_constant(compressor, consequent.value, self); - + return make_node_from_constant(compressor, consequent_value, self); } } - // x=y?true:false --> x=!!y - if (consequent instanceof AST_True - && alternative instanceof AST_False) { + + // y?true:false --> !!y + if (is_true(consequent) && is_false(alternative)) { self.condition = self.condition.negate(compressor); return make_node(AST_UnaryPrefix, self.condition, { operator: "!", expression: self.condition }); } - // x=y?false:true --> x=!y - if (consequent instanceof AST_False - && alternative instanceof AST_True) { + // y?false:true --> !y + if (is_false(consequent) && is_true(alternative)) { return self.condition.negate(compressor) } return self; + + // AST_True or !0 + function is_true(node) { + return node instanceof AST_True + || (node instanceof AST_UnaryPrefix + && node.operator == "!" + && node.expression instanceof AST_Constant + && !node.expression.value); + } + // AST_False or !1 + function is_false(node) { + return node instanceof AST_False + || (node instanceof AST_UnaryPrefix + && node.operator == "!" + && node.expression instanceof AST_Constant + && !!node.expression.value); + } }); OPT(AST_Boolean, function(self, compressor){ @@ -2569,4 +2616,11 @@ merge(Compressor.prototype, { OPT(AST_Object, literals_in_boolean_context); OPT(AST_RegExp, literals_in_boolean_context); + OPT(AST_Return, function(self, compressor){ + if (self.value instanceof AST_Undefined) { + self.value = null; + } + return self; + }); + })(); diff --git a/lib/mozilla-ast.js b/lib/mozilla-ast.js index ac53ca27..c1b2b683 100644 --- a/lib/mozilla-ast.js +++ b/lib/mozilla-ast.js @@ -146,7 +146,14 @@ case "boolean": return new (val ? AST_True : AST_False)(args); default: - args.value = val; + var rx = M.regex; + if (rx && rx.pattern) { + // RegExpLiteral as per ESTree AST spec + args.value = new RegExp(rx.pattern, rx.flags).toString(); + } else { + // support legacy RegExp + args.value = M.regex && M.raw ? M.raw : val; + } return new AST_RegExp(args); } }, @@ -334,6 +341,19 @@ }; }); + def_to_moz(AST_RegExp, function To_Moz_RegExpLiteral(M) { + var value = M.value; + return { + type: "Literal", + value: value, + raw: value.toString(), + regex: { + pattern: value.source, + flags: value.toString().match(/[gimuy]*$/)[0] + } + }; + }); + def_to_moz(AST_Constant, function To_Moz_Literal(M) { var value = M.value; if (typeof value === 'number' && (value < 0 || (value === 0 && 1 / value < 0))) { @@ -343,13 +363,15 @@ prefix: true, argument: { type: "Literal", - value: -value + value: -value, + raw: M.start.raw } }; } return { type: "Literal", - value: value + value: value, + raw: M.start.raw }; }); @@ -369,6 +391,12 @@ /* -----[ tools ]----- */ + function raw_token(moznode) { + if (moznode.type == "Literal") { + return moznode.raw != null ? moznode.raw : moznode.value + ""; + } + } + function my_start_token(moznode) { var loc = moznode.loc, start = loc && loc.start; var range = moznode.range; @@ -379,7 +407,8 @@ pos : range ? range[0] : moznode.start, endline : start && start.line, endcol : start && start.column, - endpos : range ? range[0] : moznode.start + endpos : range ? range[0] : moznode.start, + raw : raw_token(moznode), }); }; @@ -393,7 +422,8 @@ pos : range ? range[1] : moznode.end, endline : end && end.line, endcol : end && end.column, - endpos : range ? range[1] : moznode.end + endpos : range ? range[1] : moznode.end, + raw : raw_token(moznode), }); }; diff --git a/lib/output.js b/lib/output.js index 7ca7e942..80f66e0e 100644 --- a/lib/output.js +++ b/lib/output.js @@ -95,7 +95,7 @@ function OutputStream(options) { case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; - case "\x0B": return output.option("screw_ie8") ? "\\v" : "\\x0B"; + case "\x0B": return options.screw_ie8 ? "\\v" : "\\x0B"; case "\u2028": return "\\u2028"; case "\u2029": return "\\u2029"; case '"': ++dq; return '"'; @@ -382,8 +382,13 @@ function OutputStream(options) { nodetype.DEFMETHOD("_codegen", generator); }; + var use_asm = false; + AST_Node.DEFMETHOD("print", function(stream, force_parens){ - var self = this, generator = self._codegen; + var self = this, generator = self._codegen, prev_use_asm = use_asm; + if (self instanceof AST_Directive && self.value == "use asm") { + use_asm = true; + } function doit() { self.add_comments(stream); self.add_source_map(stream); @@ -396,6 +401,9 @@ function OutputStream(options) { doit(); } stream.pop_node(); + if (self instanceof AST_Lambda) { + use_asm = prev_use_asm; + } }); AST_Node.DEFMETHOD("print_to_string", function(options){ @@ -1311,10 +1319,8 @@ function OutputStream(options) { output.print_string(self.getValue(), self.quote); }); DEFPRINT(AST_Number, function(self, output){ - if (self.literal !== undefined - && +self.literal === self.value /* paranoid check */ - && self.scope && self.scope.has_directive('use asm')) { - output.print(self.literal); + if (use_asm && self.start.raw != null) { + output.print(self.start.raw); } else { output.print(make_num(self.getValue())); } diff --git a/lib/parse.js b/lib/parse.js index 8f03435f..1a7bca94 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -190,6 +190,9 @@ function parse_js_number(num) { return parseInt(num.substr(2), 2); } else if (RE_DEC_NUMBER.test(num)) { return parseFloat(num); + } else { + var val = parseFloat(num); + if (val == num) return val; } }; @@ -291,6 +294,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { nlb : S.newline_before, file : filename }; + if (/^(?:num|string|regexp)$/i.test(type)) { + ret.raw = $TEXT.substring(ret.pos, ret.endpos); + } if (!is_comment) { ret.comments_before = S.comments_before; S.comments_before = []; @@ -341,11 +347,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (prefix) num = prefix + num; var valid = parse_js_number(num); if (!isNaN(valid)) { - var tok = token("num", valid); - if (num.indexOf('.') >= 0) { - tok.literal = num; - } - return tok; + return token("num", valid); } else { parse_error("Invalid syntax: " + num); } @@ -406,6 +408,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (octal_len > 0) ch = String.fromCharCode(parseInt(ch, 8)); else ch = read_escaped_char(true); } + else if (ch == "\n") parse_error("Unterminated string constant"); else if (ch == quote) break; ret += ch; } @@ -752,9 +755,9 @@ function parse($TEXT, options) { ); }; - function semicolon() { + function semicolon(optional) { if (is("punc", ";")) next(); - else if (!can_insert_semicolon()) unexpected(); + else if (!optional && !can_insert_semicolon()) unexpected(); }; function parenthesised() { @@ -843,7 +846,7 @@ function parse($TEXT, options) { case "do": return new AST_Do({ body : in_loop(statement), - condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(), tmp) + condition : (expect_token("keyword", "while"), tmp = parenthesised(), semicolon(true), tmp) }); case "while": @@ -1323,7 +1326,7 @@ function parse($TEXT, options) { ret = _make_symbol(AST_SymbolRef); break; case "num": - ret = new AST_Number({ start: tok, end: tok, value: tok.value, literal: tok.literal }); + ret = new AST_Number({ start: tok, end: tok, value: tok.value }); break; case "string": ret = new AST_String({ diff --git a/lib/scope.js b/lib/scope.js index 3ae51dd4..ac58ad80 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -98,6 +98,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // pass 1: setup scope chaining and handle definitions var self = this; var scope = self.parent_scope = null; + var labels = new Dictionary(); var defun = null; var nesting = 0; var in_destructuring = null; @@ -121,20 +122,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.init_scope_vars(nesting); var save_scope = node.parent_scope = scope; var save_defun = defun; + var save_labels = labels; defun = scope = node; + labels = new Dictionary(); ++nesting; descend(); --nesting; scope = save_scope; defun = save_defun; + labels = save_labels; return true; // don't descend again in TreeWalker } - if (node instanceof AST_Directive) { - node.scope = scope; - push_uniq(scope.directives, node.value); - return true; - } - if (node instanceof AST_Number) { - node.scope = scope; - return true; + if (node instanceof AST_LabeledStatement) { + var l = node.label; + if (labels.has(l.name)) { + throw new Error(string_template("Label {name} defined twice", l)); + } + labels.set(l.name, l); + descend(); + labels.del(l.name); + return true; // no descend again } if (node instanceof AST_With) { for (var s = scope; s; s = s.parent_scope) @@ -148,6 +153,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ node.object_destructuring_arg = !!in_destructuring; defun.def_variable(node); } + if (node instanceof AST_Label) { + node.thedef = node; + node.references = []; + } if (node instanceof AST_SymbolLambda) { defun.def_function(node); } @@ -178,6 +187,15 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ (options.screw_ie8 ? scope : defun) .def_variable(node); } + else if (node instanceof AST_LabelRef) { + var sym = labels.get(node.name); + if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", { + name: node.name, + line: node.start.line, + col: node.start.col + })); + node.thedef = sym; + } }); self.walk(tw); @@ -200,6 +218,10 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ cls = prev_cls; return true; } + if (node instanceof AST_LoopControl && node.label) { + node.label.thedef.references.push(node); + return true; + } if (node instanceof AST_SymbolRef) { var name = node.name; var sym = node.scope.find_variable(name); @@ -236,7 +258,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ }); AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ - this.directives = []; // contains the directives defined in this scope, i.e. "use strict" this.variables = new Dictionary(); // map name to AST_SymbolVar (variables defined in this scope; includes functions) this.functions = new Dictionary(); // map name to AST_SymbolDefun (functions defined in this scope) this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement @@ -247,10 +268,6 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); -AST_Scope.DEFMETHOD("strict", function(){ - return this.has_directive("use strict"); -}); - AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; @@ -274,11 +291,6 @@ AST_Scope.DEFMETHOD("find_variable", function(name){ || (this.parent_scope && this.parent_scope.find_variable(name)); }); -AST_Scope.DEFMETHOD("has_directive", function(value){ - return this.parent_scope && this.parent_scope.has_directive(value) - || (this.directives.indexOf(value) >= 0 ? this : null); -}); - AST_Scope.DEFMETHOD("def_function", function(symbol){ this.functions.set(symbol.name, this.def_variable(symbol)); }); diff --git a/lib/transform.js b/lib/transform.js index 266e686f..2cea8705 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker; if (y !== undefined) x = y; } } - tw.pop(); + tw.pop(this); return x; }); }; diff --git a/package.json b/package.json index 953ab1a5..6b0d2f40 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.5.0", + "version": "2.6.1", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 16ef6d66..65cfea64 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -332,53 +332,247 @@ cond_7_1: { cond_8: { options = { conditionals: true, - evaluate : true + evaluate : true, + booleans : false }; input: { var a; // compress these a = condition ? true : false; - a = !condition ? true : false; - a = condition() ? true : false; + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + if (condition) { a = true; } else { a = false; } + if (condition) { + a = !0; + } else { + a = !1; + } + a = condition ? false : true; - a = !condition ? false : true; - a = condition() ? false : true; + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + if (condition) { a = false; } else { a = true; } + if (condition) { + a = !1; + } else { + a = !0; + } + // don't compress these a = condition ? 1 : false; - a = !condition ? true : 0; - a = condition ? 1 : 0; - } expect: { var a; a = !!condition; a = !condition; a = !!condition(); + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !!condition; + a = !condition; a = !!condition; a = !condition(); + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + + a = condition ? 1 : false; + a = condition ? 0 : true; + a = condition ? 1 : 0; + } +} + +cond_8b: { + options = { + conditionals: true, + evaluate : true, + booleans : true + }; + input: { + var a; + // compress these + a = condition ? true : false; + a = !condition ? true : false; + a = condition() ? true : false; + + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + + if (condition) { + a = true; + } else { + a = false; + } + + if (condition) { + a = !0; + } else { + a = !1; + } + + a = condition ? false : true; + a = !condition ? false : true; + a = condition() ? false : true; + + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + + if (condition) { + a = false; + } else { + a = true; + } + + if (condition) { + a = !1; + } else { + a = !0; + } + + a = condition ? 1 : false; + a = !condition ? true : 0; + a = condition ? 1 : 0; + } + expect: { + var a; + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !!condition; + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + + a = condition ? 1 : !1; + a = condition ? 0 : !0; + a = condition ? 1 : 0; + } +} + +cond_8c: { + options = { + conditionals: true, + evaluate : false, + booleans : false + }; + input: { + var a; + // compress these + a = condition ? true : false; + a = !condition ? true : false; + a = condition() ? true : false; + + a = condition ? !0 : !1; + a = !condition ? !null : !2; + a = condition() ? !0 : !-3.5; + + if (condition) { + a = true; + } else { + a = false; + } + + if (condition) { + a = !0; + } else { + a = !1; + } + + a = condition ? false : true; + a = !condition ? false : true; + a = condition() ? false : true; + + a = condition ? !3 : !0; + a = !condition ? !2 : !0; + a = condition() ? !1 : !0; + + if (condition) { + a = false; + } else { + a = true; + } + + if (condition) { + a = !1; + } else { + a = !0; + } + + a = condition ? 1 : false; + a = !condition ? true : 0; + a = condition ? 1 : 0; + } + expect: { + var a; + a = !!condition; + a = !condition; + a = !!condition(); + + a = !!condition; + a = !condition; + a = condition() ? !0 : !-3.5; + + a = !!condition; + a = !!condition; + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !!condition; + a = !condition(); + + a = !condition; + a = !condition; + a = condition ? 1 : false; a = condition ? 0 : true; a = condition ? 1 : 0; diff --git a/test/compress/loops.js b/test/compress/loops.js index cdf1f045..91aa1c5f 100644 --- a/test/compress/loops.js +++ b/test/compress/loops.js @@ -121,3 +121,27 @@ drop_if_else_break_4: { for (; bar() && (x(), y(), foo());) baz(), z(), k(); } } + +parse_do_while_with_semicolon: { + options = { loops: false }; + input: { + do { + x(); + } while (false);y() + } + expect: { + do x(); while (false);y(); + } +} + +parse_do_while_without_semicolon: { + options = { loops: false }; + input: { + do { + x(); + } while (false)y() + } + expect: { + do x(); while (false);y(); + } +} \ No newline at end of file diff --git a/test/compress/return_undefined.js b/test/compress/return_undefined.js new file mode 100644 index 00000000..9662aa51 --- /dev/null +++ b/test/compress/return_undefined.js @@ -0,0 +1,124 @@ +return_undefined: { + options = { + sequences : false, + if_return : true, + evaluate : true, + dead_code : true, + conditionals : true, + comparisons : true, + booleans : true, + unused : true, + side_effects : true, + properties : true, + drop_debugger : true, + loops : true, + hoist_funs : true, + keep_fargs : true, + keep_fnames : false, + hoist_vars : true, + join_vars : true, + cascade : true, + negate_iife : true + }; + input: { + function f0() { + } + function f1() { + return undefined; + } + function f2() { + return void 0; + } + function f3() { + return void 123; + } + function f4() { + return; + } + function f5(a, b) { + console.log(a, b); + baz(a); + return; + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + return undefined; + } + function f7(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return void 0; + } + return a + b; + } + function f8(a, b) { + foo(a); + bar(b); + return void 0; + } + function f9(a, b) { + foo(a); + bar(b); + return undefined; + } + function f10() { + return false; + } + function f11() { + return null; + } + function f12() { + return 0; + } + } + expect: { + function f0() {} + function f1() {} + function f2() {} + function f3() {} + function f4() {} + function f5(a, b) { + console.log(a, b); + baz(a); + } + function f6(a, b) { + console.log(a, b); + if (a) { + foo(b); + baz(a); + return a + b; + } + } + function f7(a, b) { + console.log(a, b); + if (!a) + return a + b; + foo(b); + baz(a); + } + function f8(a, b) { + foo(a); + bar(b); + } + function f9(a, b) { + foo(a); + bar(b); + } + function f10() { + return !1; + } + function f11() { + return null; + } + function f12() { + return 0; + } + } +} diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js new file mode 100644 index 00000000..527aea04 --- /dev/null +++ b/test/compress/screw-ie8.js @@ -0,0 +1,18 @@ +do_screw: { + options = { screw_ie8: true }; + beautify = { + screw_ie8: true, + ascii_only: true + }; + + input: f("\v"); + expect_exact: 'f("\\v");'; +} + +dont_screw: { + options = { screw_ie8: false }; + beautify = { screw_ie8: false, ascii_only: true }; + + input: f("\v"); + expect_exact: 'f("\\x0B");'; +} \ No newline at end of file diff --git a/test/run-tests.js b/test/run-tests.js index 6d7f7244..c1530109 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -4,7 +4,6 @@ var U = require("../tools/node"); var path = require("path"); var fs = require("fs"); var assert = require("assert"); -var sys = require("util"); var tests_dir = path.dirname(module.filename); var failures = 0; @@ -12,8 +11,8 @@ var failed_files = {}; run_compress_tests(); if (failures) { - sys.error("\n!!! Failed " + failures + " test cases."); - sys.error("!!! " + Object.keys(failed_files).join(", ")); + console.error("\n!!! Failed " + failures + " test cases."); + console.error("!!! " + Object.keys(failed_files).join(", ")); process.exit(1); } @@ -91,14 +90,15 @@ function run_compress_tests() { warnings: false }); var cmp = new U.Compressor(options, true); + var output_options = test.beautify || {}; var expect; if (test.expect) { - expect = make_code(as_toplevel(test.expect), false); + expect = make_code(as_toplevel(test.expect), output_options); } else { expect = test.expect_exact; } var input = as_toplevel(test.input); - var input_code = make_code(test.input); + var input_code = make_code(test.input, { beautify: true }); if (test.mangle_props) { input = U.mangle_properties(input, test.mangle_props); } @@ -107,7 +107,7 @@ function run_compress_tests() { if (test.mangle) { output.mangle_names(test.mangle); } - output = make_code(output, false); + output = make_code(output, output_options); if (expect != output) { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { input: input_code, @@ -151,7 +151,7 @@ function parse_test(file) { file: file, line: node.start.line, col: node.start.col, - code: make_code(node, false) + code: make_code(node, { beautify: false }) })); } @@ -198,15 +198,15 @@ function parse_test(file) { }; } -function make_code(ast, beautify) { - if (arguments.length == 1) beautify = true; - var stream = U.OutputStream({ beautify: beautify, inline_script: true }); +function make_code(ast, options) { + options.inline_script = true; + var stream = U.OutputStream(options); ast.print(stream); return stream.get(); } function evaluate(code) { if (code instanceof U.AST_Node) - code = make_code(code); + code = make_code(code, { beautify: true }); return new Function("return(" + code + ")")(); } diff --git a/tools/node.js b/tools/node.js index 7e61d2a1..f6048661 100644 --- a/tools/node.js +++ b/tools/node.js @@ -45,7 +45,6 @@ exports.minify = function(files, options) { UglifyJS.base54.reset(); // 1. parse - var haveScope = false; var toplevel = null, sourcesContent = {}; @@ -74,7 +73,6 @@ exports.minify = function(files, options) { var compress = { warnings: options.warnings }; UglifyJS.merge(compress, options.compress); toplevel.figure_out_scope(); - haveScope = true; var sq = UglifyJS.Compressor(compress); toplevel = toplevel.transform(sq); } @@ -82,17 +80,11 @@ exports.minify = function(files, options) { // 3. mangle if (options.mangle) { toplevel.figure_out_scope(options.mangle); - haveScope = true; toplevel.compute_char_frequency(options.mangle); toplevel.mangle_names(options.mangle); } - // 4. scope (if needed) - if (!haveScope) { - toplevel.figure_out_scope(); - } - - // 5. output + // 4. output var inMap = options.inSourceMap; var output = {}; if (typeof options.inSourceMap == "string") {