diff --git a/.travis.yml b/.travis.yml index cdee4301..e199df24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,11 @@ node_js: - "0.12" - "4" - "6" + - "8" env: - UGLIFYJS_TEST_ALL=1 matrix: fast_finish: true sudo: false +cache: + directories: tmp diff --git a/README.md b/README.md index e8cca3c7..4eb76823 100644 --- a/README.md +++ b/README.md @@ -569,7 +569,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u comparison are switching. Compression only works if both `comparisons` and `unsafe_comps` are both set to true. -- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)`. +- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)` + when both `args` and `code` are string literals. - `unsafe_math` (default: false) -- optimize numerical expressions like `2 * x * 3` into `6 * x`, which may give imprecise floating point results. @@ -612,6 +613,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `if_return` -- optimizations for if/return and if/continue +- `inline` -- embed simple functions + - `join_vars` -- join consecutive `var` statements - `cascade` -- small optimization for sequences, transform `x, x` into `x` @@ -767,7 +770,7 @@ can pass additional arguments that control the code output: - `quote_style` (default `0`) -- preferred quote style for strings (affects quoted property names and directives as well): - `0` -- prefers double quotes, switches to single quotes when there are - more double quotes in the string itself. + more double quotes in the string itself. `0` is best for gzip size. - `1` -- always use single quotes - `2` -- always use double quotes - `3` -- always use the original quotes diff --git a/bin/uglifyjs b/bin/uglifyjs index f2aeb084..f4feb39a 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -30,14 +30,7 @@ else if (process.argv.indexOf("options") >= 0) program.helpInformation = functio var options = UglifyJS.default_options(); for (var option in options) { text.push("--" + (option == "output" ? "beautify" : option == "sourceMap" ? "source-map" : option) + " options:"); - var defs = options[option]; - var padding = ""; - Object.keys(defs).map(function(name) { - if (padding.length < name.length) padding = Array(name.length + 1).join(" "); - return [ name, JSON.stringify(defs[name]) ]; - }).forEach(function(tokens) { - text.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]); - }); + text.push(format_object(options[option])); text.push(""); } return text.join("\n"); @@ -157,7 +150,7 @@ if (program.verbose) { } if (program.self) { if (program.args.length) { - console.error("WARN: Ignoring input files since --self was passed"); + print_error("WARN: Ignoring input files since --self was passed"); } if (!options.wrap) options.wrap = "UglifyJS"; simple_glob(UglifyJS.FILES).forEach(function(name) { @@ -187,7 +180,7 @@ function convert_ast(fn) { function run() { UglifyJS.AST_Node.warn_function = function(msg) { - console.error("WARN:", msg); + print_error("WARN: " + msg); }; if (program.timings) options.timings = true; try { @@ -216,7 +209,7 @@ function run() { if (result.error) { var ex = result.error; if (ex.name == "SyntaxError") { - console.error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); + print_error("Parse error at " + ex.filename + ":" + ex.line + "," + ex.col); var col = ex.col; var lines = files[ex.filename].split(/\r?\n/); var line = lines[ex.line - 1]; @@ -230,17 +223,17 @@ function run() { line = line.slice(col - limit); col = limit; } - console.error(line.slice(0, 80)); - console.error(line.slice(0, col).replace(/\S/g, " ") + "^"); + print_error(line.slice(0, 80)); + print_error(line.slice(0, col).replace(/\S/g, " ") + "^"); } } if (ex.defs) { - console.error("Supported options:"); - console.error(ex.defs); + print_error("Supported options:"); + print_error(format_object(ex.defs)); } fatal(ex); } else if (program.output == "ast") { - console.log(JSON.stringify(result.ast, function(key, value) { + print(JSON.stringify(result.ast, function(key, value) { if (skip_key(key)) return; if (value instanceof UglifyJS.AST_Token) return; if (value instanceof UglifyJS.Dictionary) return; @@ -256,7 +249,7 @@ function run() { return value; }, 2)); } else if (program.output == "spidermonkey") { - console.log(JSON.stringify(UglifyJS.minify(result.code, { + print(JSON.stringify(UglifyJS.minify(result.code, { compress: false, mangle: false, output: { @@ -270,7 +263,7 @@ function run() { fs.writeFileSync(program.output + ".map", result.map); } } else { - console.log(result.code); + print(result.code); } if (program.nameCache) { fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { @@ -278,13 +271,13 @@ function run() { })); } if (result.timings) for (var phase in result.timings) { - console.error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); + print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); } } function fatal(message) { if (message instanceof Error) message = message.stack.replace(/^\S*?Error:/, "ERROR:") - console.error(message); + print_error(message); process.exit(1); } @@ -378,10 +371,10 @@ function parse_js(flag, constants) { function parse_source_map() { var parse = parse_js("sourceMap", true); return function(value, options) { - var hasContent = options && options.sourceMap && "content" in options.sourceMap; + var hasContent = options && "content" in options; var settings = parse(value, options); if (!hasContent && settings.content && settings.content != "inline") { - console.error("INFO: Using input source map:", settings.content); + print_error("INFO: Using input source map: " + settings.content); settings.content = read_file(settings.content, settings.content); } return settings; @@ -403,3 +396,25 @@ function to_cache(key) { function skip_key(key) { return skip_keys.indexOf(key) >= 0; } + +function format_object(obj) { + var lines = []; + var padding = ""; + Object.keys(obj).map(function(name) { + if (padding.length < name.length) padding = Array(name.length + 1).join(" "); + return [ name, JSON.stringify(obj[name]) ]; + }).forEach(function(tokens) { + lines.push(" " + tokens[0] + padding.slice(tokens[0].length - 2) + tokens[1]); + }); + return lines.join("\n"); +} + +function print_error(msg) { + process.stderr.write(msg); + process.stderr.write("\n"); +} + +function print(txt) { + process.stdout.write(txt); + process.stdout.write("\n"); +} diff --git a/lib/compress.js b/lib/compress.js index 62f63a96..23afb045 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -64,6 +64,7 @@ function Compressor(options, false_by_default) { hoist_vars : false, ie8 : false, if_return : !false_by_default, + inline : !false_by_default, join_vars : !false_by_default, keep_fargs : true, keep_fnames : false, @@ -1459,7 +1460,7 @@ merge(Compressor.prototype, { }); if (value && typeof value == "object") { var props = []; - for (var key in value) { + for (var key in value) if (HOP(value, key)) { props.push(make_node(AST_ObjectKeyVal, orig, { key: key, value: to_node(value[key], orig) @@ -3112,24 +3113,9 @@ 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 (compressor.option("reduce_vars") && exp instanceof AST_SymbolRef) { var fixed = exp.fixed_value(); - if (fixed instanceof AST_Defun) { - def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); - } - if (fixed instanceof AST_Function) { - exp = fixed; - if (compressor.option("unused") - && def.references.length == 1 - && !(def.scope.uses_arguments - && def.orig[0] instanceof AST_SymbolFunarg) - && !def.scope.uses_eval - && compressor.find_parent(AST_Scope) === def.scope) { - self.expression = exp; - } - } + if (fixed instanceof AST_Function) exp = fixed; } if (compressor.option("unused") && exp instanceof AST_Function @@ -3344,13 +3330,70 @@ merge(Compressor.prototype, { } } if (exp instanceof AST_Function && !self.expression.is_generator) { - if (exp.body[0] instanceof AST_Return) { - var value = exp.body[0].value; + var stat = exp.body[0]; + if (compressor.option("inline") && stat instanceof AST_Return) { + var value = stat && stat.value; if (!value || value.is_constant_expression()) { var args = self.args.concat(value || make_node(AST_Undefined, self)); return make_sequence(self, args).transform(compressor); } } + if (compressor.option("inline") + && !exp.name + && exp.body.length == 1 + && !exp.uses_arguments + && !exp.uses_eval + && !self.has_pure_annotation(compressor)) { + var value; + if (stat instanceof AST_Return) { + value = stat.value.clone(true); + } else if (stat instanceof AST_SimpleStatement) { + value = make_node(AST_UnaryPrefix, stat, { + operator: "void", + expression: stat.body.clone(true) + }); + } + if (value) { + var fn = exp.clone(); + fn.argnames = []; + fn.body = []; + if (exp.argnames.length > 0) { + fn.body.push(make_node(AST_Var, self, { + definitions: exp.argnames.map(function(sym, i) { + return make_node(AST_VarDef, sym, { + name: sym, + value: self.args[i] || make_node(AST_Undefined, self) + }); + }) + })); + } + if (self.args.length > exp.argnames.length) { + fn.body.push(make_node(AST_SimpleStatement, self, { + body: make_sequence(self, self.args.slice(exp.argnames.length)) + })); + } + fn.body.push(make_node(AST_Return, self, { + value: value + })); + var body = fn.transform(compressor).body; + if (body.length == 0) return make_node(AST_Undefined, self); + if (body.length == 1 && body[0] instanceof AST_Return) { + value = body[0].value; + if (!value) return make_node(AST_Undefined, self); + value.walk(new TreeWalker(function(node) { + if (value === self) return true; + if (node instanceof AST_SymbolRef && exp.variables.has(node.name)) { + value = self; + return true; + } + })); + if (value !== self) value = best_of(compressor, value, self); + } else { + value = self; + } + if (value !== self) return value; + } + } if (compressor.option("side_effects") && all(exp.body, is_empty)) { var args = self.args.concat(make_node(AST_Undefined, self)); return make_sequence(self, args).transform(compressor); @@ -3453,6 +3496,7 @@ merge(Compressor.prototype, { continue; } var parent = null, field; + expressions[j] = cdr = cdr.clone(); while (true) { if (cdr.equivalent_to(left)) { var car = expressions[i]; @@ -3489,7 +3533,7 @@ merge(Compressor.prototype, { break; } parent = cdr; - cdr = cdr[field]; + cdr = cdr[field] = cdr[field].clone(); } } end = i; @@ -4009,12 +4053,22 @@ merge(Compressor.prototype, { return make_node(AST_Infinity, self).optimize(compressor); } } - if (compressor.option("evaluate") - && compressor.option("reduce_vars") + if (compressor.option("reduce_vars") && is_lhs(self, compressor.parent()) !== self) { var d = self.definition(); var fixed = self.fixed_value(); - if (fixed) { + if (fixed instanceof AST_Defun) { + d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true); + } + if (compressor.option("unused") + && fixed instanceof AST_Function + && d.references.length == 1 + && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) + && !d.scope.uses_eval + && compressor.find_parent(AST_Scope) === fixed.parent_scope) { + return fixed; + } + if (compressor.option("evaluate") && fixed) { if (d.should_replace === undefined) { var init = fixed.evaluate(compressor); if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) { diff --git a/lib/minify.js b/lib/minify.js index a44444d8..a6040c0b 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -88,7 +88,7 @@ function minify(files, options) { } options.parse = options.parse || {}; options.parse.toplevel = null; - for (var name in files) { + for (var name in files) if (HOP(files, name)) { options.parse.filename = name; options.parse.toplevel = parse(files[name], options.parse); if (options.sourceMap && options.sourceMap.content == "inline") { @@ -136,7 +136,7 @@ function minify(files, options) { if (options.sourceMap.includeSources) { if (files instanceof AST_Toplevel) { throw new Error("original source content unavailable"); - } else for (var name in files) { + } else for (var name in files) if (HOP(files, name)) { options.output.source_map.get().setSourceContent(name, files[name]); } } diff --git a/lib/output.js b/lib/output.js index 42137606..bbc84681 100644 --- a/lib/output.js +++ b/lib/output.js @@ -73,6 +73,7 @@ function OutputStream(options) { shebang : true, shorthand : undefined, source_map : null, + webkit : false, width : 80, wrap_iife : false, }, true); @@ -629,6 +630,13 @@ function OutputStream(options) { return true; } + if (output.option('webkit')) { + var p = output.parent(); + if (p instanceof AST_PropAccess && p.expression === this) { + return true; + } + } + if (output.option('wrap_iife')) { var p = output.parent(); return p instanceof AST_Call && p.expression === this; diff --git a/lib/parse.js b/lib/parse.js index cf5f3285..c7431ceb 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -122,8 +122,6 @@ var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); -var REGEXP_MODIFIERS = makePredicate(characters("gmsiy")); - /* -----[ Tokenizer ]----- */ // surrogate safe regexps adapted from https://github.com/mathiasbynens/unicode-8.0.0/tree/89b412d8a71ecca9ed593d9e9fa073ab64acfebe/Binary_Property @@ -850,9 +848,7 @@ var PRECEDENCE = (function(a, ret){ {} ); -var STATEMENTS_WITH_LABELS = array_to_hash([ "for", "do", "while", "switch" ]); - -var ATOMIC_START_TOKEN = array_to_hash([ "atom", "num", "string", "regexp", "name" ]); +var ATOMIC_START_TOKEN = makePredicate([ "atom", "num", "string", "regexp", "name" ]); /* -----[ Parser ]----- */ @@ -1872,7 +1868,6 @@ function parse($TEXT, options) { var tok = S.token, ret; switch (tok.type) { case "name": - case "keyword": ret = _make_symbol(AST_SymbolRef); break; case "num": @@ -1902,13 +1897,6 @@ function parse($TEXT, options) { break; } break; - case "operator": - if (!is_identifier_string(tok.value)) { - croak("Invalid getter/setter name: " + tok.value, - tok.line, tok.col, tok.pos); - } - ret = _make_symbol(AST_SymbolRef); - break; } next(); return ret; @@ -2015,7 +2003,7 @@ function parse($TEXT, options) { if (is("template_head")) { return subscripts(template_string(), allow_calls); } - if (ATOMIC_START_TOKEN[S.token.type]) { + if (ATOMIC_START_TOKEN(S.token.type)) { return subscripts(as_atom_node(), allow_calls); } unexpected(); diff --git a/lib/propmangle.js b/lib/propmangle.js index 29fb2682..fae64cd7 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -96,7 +96,8 @@ function mangle_properties(ast, options) { reserved: null, }); - var reserved = options.reserved || []; + var reserved = options.reserved; + if (!Array.isArray(reserved)) reserved = []; if (!options.builtins) find_builtins(reserved); var cache = options.cache; diff --git a/lib/scope.js b/lib/scope.js index fcee3f43..83b7576e 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -450,7 +450,7 @@ AST_Symbol.DEFMETHOD("global", function(){ }); AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ - return defaults(options, { + options = defaults(options, { eval : false, ie8 : false, keep_classnames: false, @@ -458,6 +458,8 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ reserved : [], toplevel : false, }); + if (!Array.isArray(options.reserved)) options.reserved = []; + return options; }); AST_Toplevel.DEFMETHOD("mangle_names", function(options){ diff --git a/lib/utils.js b/lib/utils.js index e21fc5ec..76306919 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -43,13 +43,6 @@ "use strict"; -function array_to_hash(a) { - var ret = Object.create(null); - for (var i = 0; i < a.length; ++i) - ret[a[i]] = true; - return ret; -}; - function slice(a, start) { return Array.prototype.slice.call(a, start || 0); }; diff --git a/package.json b/package.json index 3a0f17d9..863399c4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.15", + "version": "3.0.16", "engines": { "node": ">=0.8.0" }, diff --git a/test/benchmark.js b/test/benchmark.js index e27ed2c3..e34f0858 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -4,6 +4,7 @@ "use strict"; var createHash = require("crypto").createHash; +var fetch = require("./fetch"); var fork = require("child_process").fork; var args = process.argv.slice(2); if (!args.length) { @@ -52,7 +53,8 @@ urls.forEach(function(url) { output: 0, log: "" }; - require(url.slice(0, url.indexOf(":"))).get(url, function(res) { + fetch(url, function(err, res) { + if (err) throw err; var uglifyjs = fork("bin/uglifyjs", args, { silent: true }); res.on("data", function(data) { results[url].input += data.length; diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 20589baa..302da2b9 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1146,7 +1146,7 @@ collapse_vars_constants: { function f3(x) { var b = x.prop; sideeffect1(); - return b + -9; + return b + (function() { return -9; })(); } } } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 73cbc7b6..7d321fcf 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -863,12 +863,12 @@ issue_1583: { expect: { function m(t) { (function(e) { - t = (function() { - return (function(a) { - return a; - })(function(a) {}); - })(); - })(); + t = e(); + })(function() { + return (function(a) { + return a; + })(function(a) {}); + }); } } } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 5810ab87..f084fda8 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -738,6 +738,7 @@ unsafe_prototype_function: { call_args: { options = { evaluate: true, + inline: true, reduce_vars: true, } input: { @@ -758,6 +759,7 @@ call_args: { call_args_drop_param: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, unused: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index e38bd90a..045ce5fe 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -21,6 +21,7 @@ iifes_returning_constants_keep_fargs_true: { join_vars : true, reduce_vars : true, cascade : true, + inline : true, } input: { (function(){ return -1.23; }()); @@ -56,6 +57,7 @@ iifes_returning_constants_keep_fargs_false: { join_vars : true, reduce_vars : true, cascade : true, + inline : true, } input: { (function(){ return -1.23; }()); @@ -82,6 +84,7 @@ issue_485_crashing_1530: { conditionals: true, dead_code: true, evaluate: true, + inline: true, } input: { (function(a) { @@ -154,6 +157,7 @@ function_returning_constant_literal: { evaluate: true, cascade: true, unused: true, + inline: true, } input: { function greeter() { @@ -267,3 +271,68 @@ issue_203: { } expect_stdout: "42" } + +no_webkit: { + beautify = { + webkit: false, + } + input: { + console.log(function() { + 1 + 1; + }.a = 1); + } + expect_exact: "console.log(function(){1+1}.a=1);" + expect_stdout: "1" +} + +webkit: { + beautify = { + webkit: true, + } + input: { + console.log(function() { + 1 + 1; + }.a = 1); + } + expect_exact: "console.log((function(){1+1}).a=1);" + expect_stdout: "1" +} + +issue_2084: { + options = { + collapse_vars: true, + conditionals: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + sequences: true, + side_effects: true, + unused: true, + } + input: { + var c = 0; + !function() { + !function(c) { + c = 1 + c; + var c = 0; + function f14(a_1) { + if (c = 1 + c, 0 !== 23..toString()) + c = 1 + c, a_1 && (a_1[0] = 0); + } + f14(); + }(-1); + }(); + console.log(c); + } + expect: { + var c = 0; + !function(c) { + c = 1 + c, + c = 1 + (c = 0), + 0 !== 23..toString() && (c = 1 + c); + }(-1), + console.log(c); + } + expect_stdout: "0" +} diff --git a/test/compress/issue-1787.js b/test/compress/issue-1787.js index 02fa0f91..2b5372be 100644 --- a/test/compress/issue-1787.js +++ b/test/compress/issue-1787.js @@ -1,6 +1,7 @@ unary_prefix: { options = { evaluate: true, + inline: true, reduce_vars: true, unused: true, } diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js new file mode 100644 index 00000000..9b8c8bfd --- /dev/null +++ b/test/compress/issue-281.js @@ -0,0 +1,483 @@ +collapse_vars_constants: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + function f1(x) { + var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2(); + return b + (function() { return d - a * e - c; })(); + } + function f2(x) { + var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2(); + return b + (function() { return -a * e - c; })(); + } + } + expect: { + function f1(x) { + var b = x.prop, d = sideeffect1(), e = sideeffect2(); + return b + (d - 4 * e - 5); + } + function f2(x) { + var b = x.prop; + sideeffect1(); + return b + (-4 * sideeffect2() - 5); + } + } +} + +modified: { + options = { + collapse_vars: true, + inline: true, + unused: true, + } + input: { + function f5(b) { + var a = function() { + return b; + }(); + return b++ + a; + } + console.log(f5(1)); + } + expect: { + function f5(b) { + var a = b; + return b++ + a; + } + console.log(f5(1)); + } + expect_stdout: "2" +} + +ref_scope: { + options = { + collapse_vars: true, + inline: true, + unused: true, + } + input: { + console.log(function() { + var a = 1, b = 2, c = 3; + var a = c++, b = b /= a; + return function() { + return a; + }() + b; + }()); + } + expect: { + console.log(function() { + var a = 1, b = 2, c = 3; + b = b /= a = c++; + return a + b; + }()); + } + expect_stdout: true +} + +safe_undefined: { + options = { + conditionals: true, + if_return: true, + inline: true, + unsafe: false, + unused: true, + } + 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(a ? b : c ? d : void 0); + } + expect_stdout: true +} + +negate_iife_3: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + t ? console.log(true) : console.log(false); + } +} + +negate_iife_3_off: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: false, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + } + expect: { + t ? console.log(true) : console.log(false); + } +} + +negate_iife_4: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + sequences: true, + } + input: { + (function(){ return t })() ? console.log(true) : console.log(false); + (function(){ + console.log("something"); + })(); + } + expect: { + t ? console.log(true) : console.log(false), void console.log("something"); + } +} + +negate_iife_5: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: true, + sequences: true, + } + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + t ? foo(true) : bar(false), void console.log("something"); + } +} + +negate_iife_5_off: { + options = { + conditionals: true, + expression: true, + inline: true, + negate_iife: false, + sequences: true, + }; + input: { + if ((function(){ return t })()) { + foo(true); + } else { + bar(false); + } + (function(){ + console.log("something"); + })(); + } + expect: { + t ? foo(true) : bar(false), void console.log("something"); + } +} + +issue_1254_negate_iife_true: { + options = { + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: 'void console.log("test");' + expect_stdout: true +} + +issue_1254_negate_iife_nested: { + options = { + expression: true, + inline: true, + negate_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()()()()(); + } + expect_exact: '(void console.log("test"))()()();' +} + +negate_iife_issue_1073: { + options = { + conditionals: true, + evaluate: true, + inline: true, + negate_iife: true, + reduce_vars: true, + sequences: true, + unused: true, + }; + input: { + new (function(a) { + return function Foo() { + this.x = a; + console.log(this); + }; + }(7))(); + } + expect: { + new function() { + this.x = 7, + console.log(this); + }(); + } + expect_stdout: true +} + +issue_1288_side_effects: { + options = { + conditionals: true, + evaluate: true, + inline: true, + negate_iife: true, + reduce_vars: true, + side_effects: true, + unused: true, + }; + input: { + if (w) ; + else { + (function f() {})(); + } + if (!x) { + (function() { + x = {}; + })(); + } + if (y) + (function() {})(); + else + (function(z) { + return z; + })(0); + } + expect: { + w; + x || (x = {}); + y; + } +} + +inner_var_for_in_1: { + options = { + evaluate: true, + inline: true, + reduce_vars: true, + } + input: { + 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); + } + } + expect: { + function f() { + var a = 1, b = 2; + for (b in x(1, b, c)) { + var c = 3, d = 4; + x(1, b, c, d); + } + x(1, b, c, d); + } + } +} + +issue_1595_3: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + unused: true, + } + input: { + (function f(a) { + return g(a + 1); + })(2); + } + expect: { + g(3); + } +} + +issue_1758: { + options = { + inline: true, + sequences: true, + side_effects: true, + } + input: { + console.log(function(c) { + var undefined = 42; + return function() { + c--; + c--, c.toString(); + return; + }(); + }()); + } + expect: { + console.log(function(c) { + var undefined = 42; + return c--, c--, void c.toString(); + }()); + } + expect_stdout: "undefined" +} +wrap_iife: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + (function() { + return function() { + console.log('test') + }; + })()(); + } + expect_exact: 'void console.log("test");' +} + +wrap_iife_in_expression: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + foo = (function () { + return bar(); + })(); + } + expect_exact: 'foo=bar();' +} + +wrap_iife_in_return_call: { + options = { + inline: true, + negate_iife: false, + } + beautify = { + wrap_iife: true, + } + input: { + (function() { + return (function() { + console.log('test') + })(); + })()(); + } + expect_exact: '(void console.log("test"))();' +} + +pure_annotation: { + options = { + inline: true, + side_effects: true, + } + input: { + /*@__PURE__*/(function() { + console.log("hello"); + }()); + } + expect_exact: "" +} + +drop_fargs: { + options = { + cascade: true, + inline: true, + keep_fargs: false, + side_effects: true, + unused: true, + } + input: { + var a = 1; + !function(a_1) { + a++; + }(a++ + (a && a.var)); + console.log(a); + } + expect: { + var a = 1; + !function() { + a++; + }(++a && a.var); + console.log(a); + } + expect_stdout: "3" +} + +keep_fargs: { + options = { + cascade: true, + inline: true, + keep_fargs: true, + side_effects: true, + unused: true, + } + input: { + var a = 1; + !function(a_1) { + a++; + }(a++ + (a && a.var)); + console.log(a); + } + expect: { + var a = 1; + !function(a_1) { + a++; + }(++a && a.var); + console.log(a); + } + expect_stdout: "3" +} diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js index 514a15c7..66d24270 100644 --- a/test/compress/negate-iife.js +++ b/test/compress/negate-iife.js @@ -22,7 +22,8 @@ negate_iife_1_off: { negate_iife_2: { options = { - negate_iife: true + inline: true, + negate_iife: true, }; input: { (function(){ return {} })().x = 10; @@ -32,6 +33,7 @@ negate_iife_2: { negate_iife_2_side_effects: { options = { + inline: true, negate_iife: true, side_effects: true, } @@ -58,6 +60,7 @@ negate_iife_3_evaluate: { options = { conditionals: true, evaluate: true, + inline: true, negate_iife: true, } input: { @@ -100,6 +103,7 @@ negate_iife_3_off_evaluate: { options = { conditionals: true, evaluate: true, + inline: true, negate_iife: false, } input: { diff --git a/test/compress/node_version.js b/test/compress/node_version.js index ad0bfa15..ef62fd70 100644 --- a/test/compress/node_version.js +++ b/test/compress/node_version.js @@ -1,4 +1,4 @@ -eval_let: { +eval_let_6: { input: { eval("let a;"); console.log(); @@ -10,3 +10,29 @@ eval_let: { expect_stdout: "" node_version: ">=6" } + +eval_let_4: { + input: { + eval("let a;"); + console.log(); + } + expect: { + eval("let a;"); + console.log(); + } + expect_stdout: SyntaxError("Block-scoped declarations (let, const, function, class) not yet supported outside strict mode") + node_version: "4" +} + +eval_let_0: { + input: { + eval("let a;"); + console.log(); + } + expect: { + eval("let a;"); + console.log(); + } + expect_stdout: SyntaxError("Unexpected identifier") + node_version: "<=0.12" +} diff --git a/test/compress/properties.js b/test/compress/properties.js index 6468ff60..b7e63933 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -558,7 +558,75 @@ native_prototype: { } } -issue_2040: { +accessor_boolean: { + input: { + var a = 1; + var b = { + get true() { + return a; + }, + set false(c) { + a = c; + } + }; + console.log(b.true, b.false = 2, b.true); + } + expect_exact: 'var a=1;var b={get true(){return a},set false(c){a=c}};console.log(b.true,b.false=2,b.true);' + expect_stdout: "1 2 2" +} + +accessor_get_set: { + input: { + var a = 1; + var b = { + get set() { + return a; + }, + set get(c) { + a = c; + } + }; + console.log(b.set, b.get = 2, b.set); + } + expect_exact: 'var a=1;var b={get set(){return a},set get(c){a=c}};console.log(b.set,b.get=2,b.set);' + expect_stdout: "1 2 2" +} + +accessor_null_undefined: { + input: { + var a = 1; + var b = { + get null() { + return a; + }, + set undefined(c) { + a = c; + } + }; + console.log(b.null, b.undefined = 2, b.null); + } + expect_exact: 'var a=1;var b={get null(){return a},set undefined(c){a=c}};console.log(b.null,b.undefined=2,b.null);' + expect_stdout: "1 2 2" +} + +accessor_number: { + input: { + var a = 1; + var b = { + get 42() { + return a; + }, + set 42(c) { + a = c; + } + }; + console.log(b[42], b[42] = 2, b[42]); + } + expect_exact: 'var a=1;var b={get 42(){return a},set 42(c){a=c}};console.log(b[42],b[42]=2,b[42]);' + expect_stdout: "1 2 2" +} + +accessor_string: { input: { var a = 1; var b = { @@ -574,3 +642,20 @@ issue_2040: { expect_exact: 'var a=1;var b={get"a-b"(){return a},set"a-b"(c){a=c}};console.log(b["a-b"],b["a-b"]=2,b["a-b"]);' expect_stdout: "1 2 2" } + +accessor_this: { + input: { + var a = 1; + var b = { + get this() { + return a; + }, + set this(c) { + a = c; + } + }; + console.log(b.this, b.this = 2, b.this); + } + expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);' + expect_stdout: "1 2 2" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index c185b717..576b8b76 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2,6 +2,7 @@ reduce_vars: { options = { conditionals : true, evaluate : true, + inline : true, global_defs : { C : 0 }, @@ -1032,6 +1033,7 @@ defun_inline_2: { defun_inline_3: { options = { evaluate: true, + inline: true, passes: 2, reduce_vars: true, side_effects: true, @@ -1054,6 +1056,7 @@ defun_inline_3: { defun_call: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1080,6 +1083,7 @@ defun_call: { defun_redefine: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1112,6 +1116,7 @@ defun_redefine: { func_inline: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1138,6 +1143,7 @@ func_inline: { func_modified: { options = { + inline: true, reduce_vars: true, unused: true, } @@ -1311,19 +1317,47 @@ iife_func_side_effects: { unused: true, } input: { + function x() { + console.log("x"); + } + function y() { + console.log("y"); + } + function z() { + console.log("z"); + } (function(a, b, c) { - return b(); + function y() { + console.log("FAIL"); + } + return y + b(); })(x(), function() { return y(); }, z()); } expect: { + function x() { + console.log("x"); + } + function y() { + console.log("y"); + } + function z() { + console.log("z"); + } (function(a, b, c) { return function() { - return y(); - }(); - })(x(), 0, z()); + console.log("FAIL"); + } + b(); + })(x(), function() { + return y(); + }, z()); } + expect_stdout: [ + "x", + "z", + "y", + ] } issue_1595_1: { @@ -1687,6 +1721,7 @@ redefine_arguments_1: { redefine_arguments_2: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, side_effects: true, @@ -1723,6 +1758,7 @@ redefine_arguments_2: { redefine_arguments_3: { options = { evaluate: true, + inline: true, keep_fargs: false, passes: 3, reduce_vars: true, @@ -1799,6 +1835,7 @@ redefine_farg_1: { redefine_farg_2: { options = { evaluate: true, + inline: true, keep_fargs: false, reduce_vars: true, side_effects: true, @@ -1835,6 +1872,7 @@ redefine_farg_2: { redefine_farg_3: { options = { evaluate: true, + inline: true, keep_fargs: false, passes: 3, reduce_vars: true, diff --git a/test/compress/sandbox.js b/test/compress/sandbox.js new file mode 100644 index 00000000..6c2be933 --- /dev/null +++ b/test/compress/sandbox.js @@ -0,0 +1,14 @@ +console_log: { + input: { + console.log("%% %s"); + console.log("%% %s", "%s"); + } + expect: { + console.log("%% %s"); + console.log("%% %s", "%s"); + } + expect_stdout: [ + "%% %s", + "% %s", + ] +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 9edf627e..2752f290 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -734,3 +734,23 @@ reassign_const: { } expect_stdout: true } + +issue_2062: { + options = { + booleans: true, + cascade: true, + conditionals: true, + side_effects: true, + } + input: { + var a = 1; + if ([ a || a++ + a--, a++ + a--, a && a.var ]); + console.log(a); + } + expect: { + var a = 1; + a || (a++, a--), a++, --a && a.var; + console.log(a); + } + expect_stdout: "1" +} diff --git a/test/fetch.js b/test/fetch.js new file mode 100644 index 00000000..5ca95bbb --- /dev/null +++ b/test/fetch.js @@ -0,0 +1,31 @@ +var fs = require("fs"); +var path = require("path"); + +try { + fs.mkdirSync("./tmp"); +} catch (e) { + if (e.code != "EEXIST") throw e; +} + +function local(url) { + return path.join("./tmp", encodeURIComponent(url)); +} + +function read(url) { + return fs.createReadStream(local(url)); +} + +module.exports = function(url, callback) { + var result = read(url); + result.on("error", function(e) { + if (e.code != "ENOENT") return callback(e); + require(url.slice(0, url.indexOf(":"))).get(url, function(res) { + if (res.statusCode !== 200) return callback(res); + res.pipe(fs.createWriteStream(local(url)).on("close", function() { + callback(null, read(url)); + })); + }); + }).on("open", function() { + callback(null, result); + }); +}; diff --git a/test/input/issue-2082/sample.js b/test/input/issue-2082/sample.js new file mode 100644 index 00000000..f92e3a10 --- /dev/null +++ b/test/input/issue-2082/sample.js @@ -0,0 +1 @@ +console.log(x); \ No newline at end of file diff --git a/test/input/issue-2082/sample.js.map b/test/input/issue-2082/sample.js.map new file mode 100644 index 00000000..88b42f5a --- /dev/null +++ b/test/input/issue-2082/sample.js.map @@ -0,0 +1 @@ +{"version": 3,"sources": ["index.js"],"mappings": ";"} diff --git a/test/jetstream.js b/test/jetstream.js index 4f13a281..1cc6d4a7 100644 --- a/test/jetstream.js +++ b/test/jetstream.js @@ -3,7 +3,7 @@ "use strict"; -var site = "http://browserbench.org/JetStream/"; +var site = "http://browserbench.org/JetStream"; if (typeof phantom == "undefined") { // workaround for tty output truncation upon process.exit() [process.stdout, process.stderr].forEach(function(stream){ @@ -11,45 +11,69 @@ if (typeof phantom == "undefined") { stream._handle.setBlocking(true); }); var args = process.argv.slice(2); + var debug = args.indexOf("--debug"); + if (debug >= 0) { + args.splice(debug, 1); + debug = true; + } else { + debug = false; + } if (!args.length) { - args.push("-mc"); + args.push("-mcb", "beautify=false,webkit"); } args.push("--timings"); var child_process = require("child_process"); - try { - require("phantomjs-prebuilt"); - } catch(e) { - child_process.execSync("npm install phantomjs-prebuilt@2.1.14"); - } + var fetch = require("./fetch"); var http = require("http"); var server = http.createServer(function(request, response) { request.resume(); - var url = decodeURIComponent(request.url.slice(1)); - var stderr = ""; - var uglifyjs = child_process.fork("bin/uglifyjs", args, { - silent: true - }).on("exit", function(code) { - console.log("uglifyjs", url.indexOf(site) == 0 ? url.slice(site.length) : url, args.join(" ")); - console.log(stderr); - if (code) throw new Error("uglifyjs failed with code " + code); - }); - uglifyjs.stderr.on("data", function(data) { - stderr += data; - }).setEncoding("utf8"); - uglifyjs.stdout.pipe(response); - http.get(url, function(res) { - res.pipe(uglifyjs.stdin); - }); - }).listen().on("listening", function() { - var phantomjs = require("phantomjs-prebuilt"); - var program = phantomjs.exec(process.argv[1], server.address().port); - program.stdout.pipe(process.stdout); - program.stderr.pipe(process.stderr); - program.on("exit", function(code) { - server.close(); - if (code) throw new Error("JetStream failed!"); - console.log("JetStream completed successfully."); + var url = site + request.url; + fetch(url, function(err, res) { + if (err) throw err; + response.writeHead(200, { + "Content-Type": { + css: "text/css", + js: "application/javascript", + png: "image/png" + }[url.slice(url.lastIndexOf(".") + 1)] || "text/html; charset=utf-8" + }); + if (/\.js$/.test(url)) { + var stderr = ""; + var uglifyjs = child_process.fork("bin/uglifyjs", args, { + silent: true + }).on("exit", function(code) { + console.log("uglifyjs", url.slice(site.length + 1), args.join(" ")); + console.log(stderr); + if (code) throw new Error("uglifyjs failed with code " + code); + }); + uglifyjs.stderr.on("data", function(data) { + stderr += data; + }).setEncoding("utf8"); + uglifyjs.stdout.pipe(response); + res.pipe(uglifyjs.stdin); + } else { + res.pipe(response); + } }); + }).listen(); + server.on("listening", function() { + var port = server.address().port; + if (debug) { + console.log("http://localhost:" + port + "/"); + } else { + child_process.exec("npm install phantomjs-prebuilt@2.1.14 --no-save", function(error) { + if (error) throw error; + var program = require("phantomjs-prebuilt").exec(process.argv[1], port); + program.stdout.pipe(process.stdout); + program.stderr.pipe(process.stderr); + program.on("exit", function(code) { + server.close(); + if (code) throw new Error("JetStream failed!"); + console.log("JetStream completed successfully."); + process.exit(0); + }); + }); + } }); server.timeout = 0; } else { @@ -63,10 +87,6 @@ if (typeof phantom == "undefined") { phantom.exit(1); }; var url = "http://localhost:" + require("system").args[1] + "/"; - page.onResourceRequested = function(requestData, networkRequest) { - if (/\.js$/.test(requestData.url)) - networkRequest.changeUrl(url + encodeURIComponent(requestData.url)); - } page.onConsoleMessage = function(msg) { if (/Error:/i.test(msg)) { console.error(msg); @@ -77,8 +97,8 @@ if (typeof phantom == "undefined") { phantom.exit(); } }; - page.open(site, function(status) { - if (status != "success") phantomjs.exit(1); + page.open(url, function(status) { + if (status != "success") phantom.exit(1); page.evaluate(function() { JetStream.switchToQuick(); JetStream.start(); diff --git a/test/mocha/cli.js b/test/mocha/cli.js index b4d1946b..aed30925 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -77,6 +77,23 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("should not consider source map file content as source map file name (issue #2082)", function (done) { + var command = [ + uglifyjscmd, + "test/input/issue-2082/sample.js", + "--source-map", "content=test/input/issue-2082/sample.js.map", + "--source-map", "url=inline", + ].join(" "); + + exec(command, function (err, stdout, stderr) { + if (err) throw err; + + var stderrLines = stderr.split('\n'); + assert.strictEqual(stderrLines[0], 'INFO: Using input source map: test/input/issue-2082/sample.js.map'); + assert.notStrictEqual(stderrLines[1], 'INFO: Using input source map: {"version": 3,"sources": ["index.js"],"mappings": ";"}'); + done(); + }); + }); it("Should work with --keep-fnames (mangle only)", function (done) { var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m'; @@ -557,7 +574,27 @@ describe("bin/uglifyjs", function () { exec(command, function (err, stdout, stderr) { assert.ok(err); assert.strictEqual(stdout, ""); - assert.ok(/^Supported options:\n\{[^}]+}\nERROR: `ascii-only` is not a supported option/.test(stderr), stderr); + assert.ok(/^Supported options:\n[\s\S]*?\nERROR: `ascii-only` is not a supported option/.test(stderr), stderr); + done(); + }); + }); + it("Should work with --mangle reserved=[]", function (done) { + var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=[callback]'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, 'function test(callback){"aaaaaaaaaaaaaaaa";callback(err,data);callback(err,data)}\n'); + done(); + }); + }); + it("Should work with --mangle reserved=false", function (done) { + var command = uglifyjscmd + ' test/input/issue-505/input.js -m reserved=false'; + + exec(command, function (err, stdout) { + if (err) throw err; + + assert.strictEqual(stdout, 'function test(a){"aaaaaaaaaaaaaaaa";a(err,data);a(err,data)}\n'); done(); }); }); diff --git a/test/mocha/glob.js b/test/mocha/glob.js index 56d3f82a..8567ecb3 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -31,7 +31,7 @@ describe("bin/uglifyjs with input file globs", function() { exec(command, function(err, stdout) { if (err) throw err; - assert.strictEqual(stdout, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);\n'); + assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n'); done(); }); }); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 77b798ec..638e79f7 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -13,6 +13,13 @@ describe("minify", function() { assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); }); + it("Should skip inherited keys from `files`", function() { + var files = Object.create({ skip: this }); + files[0] = "alert(1 + 1)"; + var result = Uglify.minify(files); + assert.strictEqual(result.code, "alert(2);"); + }); + describe("keep_quoted_props", function() { it("Should preserve quotes in object literals", function() { var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; @@ -106,7 +113,7 @@ describe("minify", function() { content: "inline" } }); - assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();"); + assert.strictEqual(result.code, "var bar=function(){return function(bar){return bar}}();"); assert.strictEqual(warnings.length, 1); assert.strictEqual(warnings[0], "inline source map not found"); } finally { @@ -207,5 +214,17 @@ describe("minify", function() { assert.ok(err instanceof Error); assert.strictEqual(err.stack.split(/\n/)[0], "Error: Can't handle expression: debugger"); }); + it("should skip inherited properties", function() { + var foo = Object.create({ skip: this }); + foo.bar = 42; + var result = Uglify.minify("alert(FOO);", { + compress: { + global_defs: { + FOO: foo + } + } + }); + assert.strictEqual(result.code, "alert({bar:42});"); + }); }); }); diff --git a/test/mocha/release.js b/test/mocha/release.js index b73a3df7..1bf6e87b 100644 --- a/test/mocha/release.js +++ b/test/mocha/release.js @@ -1,16 +1,13 @@ var assert = require("assert"); +var semver = require("semver"); 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" + stdio: [ "ignore", 1, 2 ] }).on("exit", function(code) { - clearInterval(id); assert.strictEqual(code, 0); done(); }); @@ -36,11 +33,9 @@ describe("test/benchmark.js", function() { }); }); +if (semver.satisfies(process.version, "0.12")) return; 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", "-mc keep_fargs=false,passes=3,pure_getters,unsafe,unsafe_comps,unsafe_math,unsafe_proto", @@ -48,6 +43,7 @@ describe("test/jetstream.js", function() { it("Should pass with options " + options, function(done) { var args = options.split(/ /); args.unshift("test/jetstream.js"); + args.push("-b", "beautify=false,webkit"); run(process.argv[0], args, done); }); }); diff --git a/test/run-tests.js b/test/run-tests.js index 8427d4a8..188532ea 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -294,8 +294,24 @@ function parse_test(file) { if (label.name == "expect_exact" || label.name == "node_version") { 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; + if (stat.TYPE == "SimpleStatement") { + var body = stat.body; + if (body instanceof U.AST_Boolean) { + test[label.name] = body.value; + } else if (body instanceof U.AST_Call) { + var ctor = global[body.expression.name]; + assert.ok(ctor === Error || ctor.prototype instanceof Error, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + test[label.name] = ctor.apply(null, body.args.map(function(node) { + assert.ok(node instanceof U.AST_Constant, tmpl("Unsupported expect_stdout format [{line},{col}]", { + line: label.start.line, + col: label.start.col + })); + return node.value; + })); + } } else { test[label.name] = read_string(stat) + "\n"; } diff --git a/test/sandbox.js b/test/sandbox.js index 974f5211..fe2e588e 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -19,23 +19,25 @@ function safe_log(arg, level) { var FUNC_TOSTRING = [ "Function.prototype.toString = Function.prototype.valueOf = function() {", - " var id = 0;", + " var id = 100000;", " return function() {", ' if (this === Array) return "[Function: Array]";', ' if (this === Object) return "[Function: Object]";', " var i = this.name;", ' if (typeof i != "number") {', " i = ++id;", +].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [ ' Object.defineProperty(this, "name", {', " get: function() {", " return i;", " }", " });", +] : [], [ " }", ' return "[Function: " + i + "]";', " }", "}();", -].join("\n"); +]).join("\n"); exports.run_code = function(code) { var stdout = ""; var original_write = process.stdout.write; @@ -50,7 +52,10 @@ exports.run_code = function(code) { "}();", ].join("\n"), { console: { - log: function() { + log: function(msg) { + if (arguments.length == 1 && typeof msg == "string") { + return console.log("%s", msg); + } return console.log.apply(console, [].map.call(arguments, function(arg) { return safe_log(arg, 3); })); diff --git a/test/ufuzz.js b/test/ufuzz.js index e3361e38..e38ffa2f 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -102,23 +102,23 @@ for (var i = 2; i < process.argv.length; ++i) { case '--help': case '-h': case '-?': - console.log('** UglifyJS fuzzer help **'); - console.log('Valid options (optional):'); - console.log(': generate this many cases (if used must be first arg)'); - console.log('-v: print every generated test case'); - console.log('-V: print every 100th generated test case'); - console.log('-t : generate this many toplevels per run (more take longer)'); - console.log('-r : maximum recursion depth for generator (higher takes longer)'); - console.log('-s1 : force the first level statement to be this one (see list below)'); - console.log('-s2 : force the second level statement to be this one (see list below)'); - console.log('--no-catch-redef: do not redefine catch variables'); - console.log('--no-directive: do not generate directives'); - console.log('--use-strict: generate "use strict"'); - console.log('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); - console.log('--only-stmt : a comma delimited white list of statements that may be generated'); - console.log('--without-stmt : a comma delimited black list of statements never to generate'); - console.log('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID)); - console.log('** UglifyJS fuzzer exiting **'); + println('** UglifyJS fuzzer help **'); + println('Valid options (optional):'); + println(': generate this many cases (if used must be first arg)'); + println('-v: print every generated test case'); + println('-V: print every 100th generated test case'); + println('-t : generate this many toplevels per run (more take longer)'); + println('-r : maximum recursion depth for generator (higher takes longer)'); + println('-s1 : force the first level statement to be this one (see list below)'); + println('-s2 : force the second level statement to be this one (see list below)'); + println('--no-catch-redef: do not redefine catch variables'); + println('--no-directive: do not generate directives'); + println('--use-strict: generate "use strict"'); + println('--stmt-depth-from-func: reset statement depth counter at each function, counts from global otherwise'); + println('--only-stmt : a comma delimited white list of statements that may be generated'); + println('--without-stmt : a comma delimited black list of statements never to generate'); + println('List of accepted statement names: ' + Object.keys(STMT_ARG_TO_ID)); + println('** UglifyJS fuzzer exiting **'); return 0; default: // first arg may be a number. @@ -941,7 +941,17 @@ if (require.main !== module) { return; } -function try_beautify(code, result) { +function println(msg) { + if (typeof msg != "undefined") process.stdout.write(msg); + process.stdout.write("\n"); +} + +function errorln(msg) { + if (typeof msg != "undefined") process.stderr.write(msg); + process.stderr.write("\n"); +} + +function try_beautify(code, result, printfn) { var beautified = UglifyJS.minify(code, { compress: false, mangle: false, @@ -951,15 +961,15 @@ function try_beautify(code, result) { }, }); if (beautified.error) { - console.log("// !!! beautify failed !!!"); - console.log(beautified.error.stack); + printfn("// !!! beautify failed !!!"); + printfn(beautified.error.stack); } else if (sandbox.same_stdout(sandbox.run_code(beautified.code), result)) { - console.log("// (beautified)"); - console.log(beautified.code); + printfn("// (beautified)"); + printfn(beautified.code); return; } - console.log("//"); - console.log(code); + printfn("//"); + printfn(code); } var default_options = UglifyJS.default_options(); @@ -977,8 +987,8 @@ function log_suspects(minify_options, component) { m[component] = o; var result = UglifyJS.minify(original_code, m); if (result.error) { - console.log("Error testing options." + component + "." + name); - console.log(result.error); + errorln("Error testing options." + component + "." + name); + errorln(result.error.stack); } else { var r = sandbox.run_code(result.code); return sandbox.same_stdout(original_result, r); @@ -986,49 +996,49 @@ function log_suspects(minify_options, component) { } }); if (suspects.length > 0) { - console.log("Suspicious", component, "options:"); + errorln("Suspicious " + component + " options:"); suspects.forEach(function(name) { - console.log(" " + name); + errorln(" " + name); }); - console.log(); + errorln(); } } function log(options) { - if (!ok) console.log('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); - console.log("//============================================================="); - if (!ok) console.log("// !!!!!! Failed... round", round); - console.log("// original code"); - try_beautify(original_code, original_result); - console.log(); - console.log(); - console.log("//-------------------------------------------------------------"); + if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); + errorln("//============================================================="); + if (!ok) errorln("// !!!!!! Failed... round " + round); + errorln("// original code"); + try_beautify(original_code, original_result, errorln); + errorln(); + errorln(); + errorln("//-------------------------------------------------------------"); if (typeof uglify_code == "string") { - console.log("// uglified code"); - try_beautify(uglify_code, uglify_result); - console.log(); - console.log(); - console.log("original result:"); - console.log(original_result); - console.log("uglified result:"); - console.log(uglify_result); + errorln("// uglified code"); + try_beautify(uglify_code, uglify_result, errorln); + errorln(); + errorln(); + errorln("original result:"); + errorln(typeof original_result == "string" ? original_result : original_result.stack); + errorln("uglified result:"); + errorln(typeof uglify_result == "string" ? uglify_result : uglify_result.stack); } else { - console.log("// !!! uglify failed !!!"); - console.log(uglify_code.stack); + errorln("// !!! uglify failed !!!"); + errorln(uglify_code.stack); if (typeof original_result != "string") { - console.log(); - console.log(); - console.log("original stacktrace:"); - console.log(original_result.stack); + errorln(); + errorln(); + errorln("original stacktrace:"); + errorln(original_result.stack); } } - console.log("minify(options):"); + errorln("minify(options):"); options = JSON.parse(options); - console.log(options); - console.log(); + errorln(JSON.stringify(options, null, 2)); + errorln(); if (!ok && typeof uglify_code == "string") { Object.keys(default_options).forEach(log_suspects.bind(null, options)); - console.log("!!!!!! Failed... round", round); + errorln("!!!!!! Failed... round " + round); } } @@ -1058,19 +1068,19 @@ for (var round = 1; round <= num_iterations; round++) { } if (verbose || (verbose_interval && !(round % INTERVAL_COUNT)) || !ok) log(options); else if (typeof original_result != "string") { - console.log("//============================================================="); - console.log("// original code"); - try_beautify(original_code, original_result); - console.log(); - console.log(); - console.log("original result:"); - console.log(original_result); - console.log(); + println("//============================================================="); + println("// original code"); + try_beautify(original_code, original_result, println); + println(); + println(); + println("original result:"); + println(original_result.stack); + println(); } if (!ok && isFinite(num_iterations)) { - console.log(); + println(); process.exit(1); } }); } -console.log(); +println();