diff --git a/README.md b/README.md index 8e672b0c..047f2a28 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ a double dash to prevent input files being used as option arguments: `debug` Add debug prefix and suffix. `domprops` Mangle property names that overlaps with DOM properties. - `keep_quoted` Only mangle unquoted properies. + `keep_quoted` Only mangle unquoted properties. `regex` Only mangle matched property names. `reserved` List of names that should not be mangled. -b, --beautify [options] Beautify output/specify output options: @@ -782,7 +782,7 @@ 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_Function` (default: `false`) -- compress and mangle `Function(args, code)` when both `args` and `code` are string literals. - `unsafe_math` (default: `false`) -- optimize numerical expressions like @@ -801,6 +801,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `unsafe_regexp` (default: `false`) -- enable substitutions of variables with `RegExp` values the same way as if they are constants. +- `unsafe_undefined` (default: `false`) -- substitute `void 0` if there is a + variable named `undefined` in scope (variable name will be mangled, typically + reduced to a single character) + - `unused` (default: `true`) -- drop unreferenced functions and variables (simple direct variable assignments do not count as references unless set to `"keep_assign"`) @@ -993,9 +997,6 @@ when this flag is on: - `new Object()` → `{}` - `String(exp)` or `exp.toString()` → `"" + exp` - `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` -- `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) ### Conditional compilation diff --git a/bin/uglifyjs b/bin/uglifyjs index 26fd8efe..21635bcb 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -45,6 +45,7 @@ program.option("--ie8", "Support non-standard Internet Explorer 8."); program.option("--keep-classnames", "Do not mangle/drop class names."); program.option("--keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name."); program.option("--name-cache ", "File to hold mangled name mappings."); +program.option("--rename", "Force symbol expansion."); program.option("--no-rename", "Disable symbol expansion."); program.option("--safari10", "Support non-standard Safari 10."); program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)"); @@ -65,14 +66,12 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { "compress", "ie8", "mangle", - "rename", "safari10", "sourceMap", "toplevel", "wrap" ].forEach(function(name) { if (name in program) { - if (name == "rename" && program[name]) return; options[name] = program[name]; } }); @@ -132,6 +131,11 @@ if (program.parse) { fatal("ERROR: inline source map only works with built-in parser"); } } +if (~program.rawArgs.indexOf("--rename")) { + options.rename = true; +} else if (!program.rename) { + options.rename = false; +} var convert_path = function(name) { return name; }; diff --git a/lib/ast.js b/lib/ast.js index 7407bac9..8700b4ba 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -267,11 +267,10 @@ var AST_For = DEFNODE("For", "init condition step", { } }, AST_IterationStatement); -var AST_ForIn = DEFNODE("ForIn", "init name object", { +var AST_ForIn = DEFNODE("ForIn", "init object", { $documentation: "A `for ... in` statement", $propdoc: { init: "[AST_Node] the `for/in` initialization code", - name: "[AST_SymbolRef?] the loop variable, only if `init` is AST_Var", object: "[AST_Node] the object that we're looping through" }, _walk: function(visitor) { @@ -319,6 +318,13 @@ var AST_Scope = DEFNODE("Scope", "variables functions uses_with uses_eval parent self = self.parent_scope; } return self; + }, + clone: function(deep) { + var node = this._clone(deep); + if (this.variables) node.variables = this.variables.clone(); + if (this.functions) node.functions = this.functions.clone(); + if (this.enclosed) node.enclosed = this.enclosed.slice(); + return node; } }, AST_Block); @@ -863,8 +869,8 @@ var AST_Object = DEFNODE("Object", "properties", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { $documentation: "Base class for literal object properties", $propdoc: { - key: "[string|AST_Node] the property name converted to a string for ObjectKeyVal. For setters, getters and computed property this is an arbitrary AST_Node", - value: "[AST_Node] property value. For setters and getters this is an AST_Accessor." + key: "[string|AST_Node] property name. For ObjectKeyVal this is a string. For getters, setters and computed property this is an AST_Node.", + value: "[AST_Node] property value. For getters and setters this is an AST_Accessor." }, _walk: function(visitor) { return visitor._visit(this, function(){ diff --git a/lib/compress.js b/lib/compress.js index f7e49424..2fd8c4fb 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -89,11 +89,12 @@ function Compressor(options, false_by_default) { unsafe : false, unsafe_arrows : false, unsafe_comps : false, - unsafe_Func : false, + unsafe_Function: false, unsafe_math : false, unsafe_methods: false, unsafe_proto : false, unsafe_regexp : false, + unsafe_undefined: false, unused : !false_by_default, warnings : false, }, true); @@ -178,7 +179,8 @@ merge(Compressor.prototype, { node.process_expression(true); } var passes = +this.options.passes || 1; - var last_count = 1 / 0; + var min_count = 1 / 0; + var stopping = false; var mangle = { ie8: this.option("ie8") }; for (var pass = 0; pass < passes; pass++) { node.figure_out_scope(mangle); @@ -190,9 +192,15 @@ merge(Compressor.prototype, { node.walk(new TreeWalker(function() { count++; })); - this.info("pass " + pass + ": last_count: " + last_count + ", count: " + count); - if (count >= last_count) break; - last_count = count; + this.info("pass " + pass + ": last_count: " + min_count + ", count: " + count); + if (count < min_count) { + min_count = count; + stopping = false; + } else if (stopping) { + break; + } else { + stopping = true; + } } } if (this.option("expression")) { @@ -411,14 +419,15 @@ merge(Compressor.prototype, { } function read_property(obj, key) { - if (key instanceof AST_Constant) key = key.getValue(); - if (key instanceof AST_Node) return null; + key = get_value(key); + if (key instanceof AST_Node) return; var value; if (obj instanceof AST_Array) { var elements = obj.elements; if (key == "length") return make_node_from_constant(elements.length, obj); if (typeof key == "number" && key in elements) value = elements[key]; } else if (obj instanceof AST_Object) { + key = "" + key; var props = obj.properties; for (var i = props.length; --i >= 0;) { var prop = props[i]; @@ -918,9 +927,8 @@ merge(Compressor.prototype, { } function loop_body(x) { - if (x instanceof AST_Switch) return x; - if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { - return (x.body instanceof AST_BlockStatement ? x.body : x); + if (x instanceof AST_IterationStatement) { + return x.body instanceof AST_BlockStatement ? x.body : x; } return x; }; @@ -964,7 +972,7 @@ merge(Compressor.prototype, { sequencesize_2(statements, compressor); } if (compressor.option("join_vars")) { - join_consecutive_vars(statements, compressor); + join_consecutive_vars(statements); } if (compressor.option("collapse_vars")) { collapse(statements, compressor); @@ -1281,14 +1289,31 @@ merge(Compressor.prototype, { expr.definitions.forEach(extract_candidates); } else if (expr instanceof AST_DWLoop) { extract_candidates(expr.condition); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } } else if (expr instanceof AST_Exit) { if (expr.value) extract_candidates(expr.value); } else if (expr instanceof AST_For) { if (expr.init) extract_candidates(expr.init); if (expr.condition) extract_candidates(expr.condition); if (expr.step) extract_candidates(expr.step); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } + } else if (expr instanceof AST_ForIn) { + extract_candidates(expr.object); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } } else if (expr instanceof AST_If) { extract_candidates(expr.condition); + if (!(expr.body instanceof AST_Block)) { + extract_candidates(expr.body); + } + if (expr.alternative && !(expr.alternative instanceof AST_Block)) { + extract_candidates(expr.alternative); + } } else if (expr instanceof AST_Sequence) { expr.expressions.forEach(extract_candidates); } else if (expr instanceof AST_SimpleStatement) { @@ -1315,10 +1340,12 @@ merge(Compressor.prototype, { if (parent instanceof AST_Call) return node; if (parent instanceof AST_Case) return node; if (parent instanceof AST_Conditional) return node; + if (parent instanceof AST_Definitions) return find_stop(parent, level + 1); if (parent instanceof AST_Exit) return node; if (parent instanceof AST_If) return node; if (parent instanceof AST_IterationStatement) return node; if (parent instanceof AST_Sequence) return find_stop(parent, level + 1); + if (parent instanceof AST_SimpleStatement) return find_stop(parent, level + 1); if (parent instanceof AST_Switch) return node; if (parent instanceof AST_VarDef) return node; return null; @@ -1754,37 +1781,34 @@ merge(Compressor.prototype, { for (var i = 0; i < statements.length; i++) { var stat = statements[i]; if (prev) { - if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) { - var abort = false; - prev.body.walk(new TreeWalker(function(node) { - if (abort || node instanceof AST_Scope) return true; - if (node instanceof AST_Binary && node.operator == "in") { - abort = true; - return true; - } - })); - if (!abort) { - if (stat.init) stat.init = cons_seq(stat.init); - else { - stat.init = prev.body; - n--; - CHANGED = true; + if (stat instanceof AST_Exit) { + stat.value = cons_seq(stat.value || make_node(AST_Undefined, stat).transform(compressor)); + } else if (stat instanceof AST_For) { + if (!(stat.init instanceof AST_Definitions)) { + var abort = false; + prev.body.walk(new TreeWalker(function(node) { + if (abort || node instanceof AST_Scope) return true; + if (node instanceof AST_Binary && node.operator == "in") { + abort = true; + return true; + } + })); + if (!abort) { + if (stat.init) stat.init = cons_seq(stat.init); + else { + stat.init = prev.body; + n--; + CHANGED = true; + } } } - } - else if (stat instanceof AST_If) { + } else if (stat instanceof AST_ForIn) { + stat.object = cons_seq(stat.object); + } else if (stat instanceof AST_If) { stat.condition = cons_seq(stat.condition); - } - else if (stat instanceof AST_With) { + } else if (stat instanceof AST_Switch) { stat.expression = cons_seq(stat.expression); - } - else if (stat instanceof AST_Exit && stat.value) { - stat.value = cons_seq(stat.value); - } - else if (stat instanceof AST_Exit) { - stat.value = cons_seq(make_node(AST_Undefined, stat).transform(compressor)); - } - else if (stat instanceof AST_Switch) { + } else if (stat instanceof AST_With) { stat.expression = cons_seq(stat.expression); } } @@ -1840,6 +1864,12 @@ merge(Compressor.prototype, { prop = prop.evaluate(compressor); } if (prop instanceof AST_Node) break; + prop = "" + prop; + if (compressor.has_directive("use strict")) { + if (!all(def.value.properties, function(node) { + return node.key != prop && node.key.name != prop; + })) break; + } def.value.properties.push(make_node(AST_ObjectKeyVal, node, { key: prop, value: node.right @@ -1850,7 +1880,7 @@ merge(Compressor.prototype, { return trimmed && exprs; } - function join_consecutive_vars(statements, compressor) { + function join_consecutive_vars(statements) { var defs; for (var i = 0, j = -1, len = statements.length; i < len; i++) { var stat = statements[i]; @@ -1867,18 +1897,7 @@ merge(Compressor.prototype, { defs = stat; } } else if (stat instanceof AST_Exit) { - var exprs = join_object_assignments(prev, stat.value); - if (exprs) { - CHANGED = true; - if (exprs.length) { - stat.value = make_sequence(stat.value, exprs); - } else if (stat.value instanceof AST_Sequence) { - stat.value = stat.value.tail_node().left; - } else { - stat.value = stat.value.left; - } - } - statements[++j] = stat; + stat.value = extract_object_assignments(stat.value); } else if (stat instanceof AST_For) { var exprs = join_object_assignments(prev, stat.init); if (exprs) { @@ -1900,6 +1919,10 @@ merge(Compressor.prototype, { } else { statements[++j] = stat; } + } else if (stat instanceof AST_ForIn) { + stat.object = extract_object_assignments(stat.object); + } else if (stat instanceof AST_If) { + stat.condition = extract_object_assignments(stat.condition); } else if (stat instanceof AST_SimpleStatement) { var exprs = join_object_assignments(prev, stat.body); if (exprs) { @@ -1908,11 +1931,31 @@ merge(Compressor.prototype, { stat.body = make_sequence(stat.body, exprs); } statements[++j] = stat; + } else if (stat instanceof AST_Switch) { + stat.expression = extract_object_assignments(stat.expression); + } else if (stat instanceof AST_With) { + stat.expression = extract_object_assignments(stat.expression); } else { statements[++j] = stat; } } statements.length = j + 1; + + function extract_object_assignments(value) { + statements[++j] = stat; + var exprs = join_object_assignments(prev, value); + if (exprs) { + CHANGED = true; + if (exprs.length) { + return make_sequence(value, exprs); + } else if (value instanceof AST_Sequence) { + return value.tail_node().left; + } else { + return value.left; + } + } + return value; + } } } @@ -1944,6 +1987,18 @@ merge(Compressor.prototype, { })); }; + function get_value(key) { + if (key instanceof AST_Constant) { + return key.getValue(); + } + if (key instanceof AST_UnaryPrefix + && key.operator == "void" + && key.expression instanceof AST_Constant) { + return; + } + return key; + } + function is_undefined(node, compressor) { return node.is_undefined || node instanceof AST_Undefined @@ -2192,6 +2247,95 @@ merge(Compressor.prototype, { return (first_in_statement(compressor) ? best_of_statement : best_of_expression)(ast1, ast2); } + function convert_to_predicate(obj) { + for (var key in obj) { + obj[key] = makePredicate(obj[key]); + } + } + + var object_fns = [ + "constructor", + "toString", + "valueOf", + ]; + var native_fns = { + Array: [ + "indexOf", + "join", + "lastIndexOf", + "slice", + ].concat(object_fns), + Boolean: object_fns, + Number: [ + "toExponential", + "toFixed", + "toPrecision", + ].concat(object_fns), + Object: object_fns, + RegExp: [ + "test", + ].concat(object_fns), + String: [ + "charAt", + "charCodeAt", + "concat", + "indexOf", + "italics", + "lastIndexOf", + "match", + "replace", + "search", + "slice", + "split", + "substr", + "substring", + "trim", + ].concat(object_fns), + }; + convert_to_predicate(native_fns); + var static_fns = { + Array: [ + "isArray", + ], + Math: [ + "abs", + "acos", + "asin", + "atan", + "ceil", + "cos", + "exp", + "floor", + "log", + "round", + "sin", + "sqrt", + "tan", + "atan2", + "pow", + "max", + "min", + ], + Number: [ + "isFinite", + "isNaN", + ], + Object: [ + "create", + "getOwnPropertyDescriptor", + "getOwnPropertyNames", + "getPrototypeOf", + "isExtensible", + "isFrozen", + "isSealed", + "keys", + ], + String: [ + "fromCharCode", + ], + }; + convert_to_predicate(static_fns); + // methods to evaluate a constant expression (function(def){ // If the node has been successfully reduced to a constant, @@ -2234,7 +2378,10 @@ merge(Compressor.prototype, { var elements = []; for (var i = 0, len = this.elements.length; i < len; i++) { var element = this.elements[i]; - if (element instanceof AST_Function) continue; + if (element instanceof AST_Function) { + elements.push(element); + continue; + } var value = element._eval(compressor, depth); if (element === value) return this; elements.push(value); @@ -2364,13 +2511,9 @@ merge(Compressor.prototype, { Array: Array, Math: Math, Number: Number, + Object: Object, String: String, }; - function convert_to_predicate(obj) { - for (var key in obj) { - obj[key] = makePredicate(obj[key]); - } - } var static_values = { Math: [ "E", @@ -2411,77 +2554,6 @@ merge(Compressor.prototype, { } return this; }); - var object_fns = [ - "constructor", - "toString", - "valueOf", - ]; - var native_fns = { - Array: [ - "indexOf", - "join", - "lastIndexOf", - "slice", - ].concat(object_fns), - Boolean: object_fns, - Number: [ - "toExponential", - "toFixed", - "toPrecision", - ].concat(object_fns), - RegExp: [ - "test", - ].concat(object_fns), - String: [ - "charAt", - "charCodeAt", - "concat", - "indexOf", - "italics", - "lastIndexOf", - "match", - "replace", - "search", - "slice", - "split", - "substr", - "substring", - "trim", - ].concat(object_fns), - }; - convert_to_predicate(native_fns); - var static_fns = { - Array: [ - "isArray", - ], - Math: [ - "abs", - "acos", - "asin", - "atan", - "ceil", - "cos", - "exp", - "floor", - "log", - "round", - "sin", - "sqrt", - "tan", - "atan2", - "pow", - "max", - "min" - ], - Number: [ - "isFinite", - "isNaN", - ], - String: [ - "fromCharCode", - ], - }; - convert_to_predicate(static_fns); def(AST_Call, function(compressor, depth) { var exp = this.expression; if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { @@ -2506,7 +2578,16 @@ merge(Compressor.prototype, { if (arg === value) return this; args.push(value); } - return val[key].apply(val, args); + try { + return val[key].apply(val, args); + } catch (ex) { + compressor.warn("Error evaluating {code} [{file}:{line},{col}]", { + code: this.print_to_string(), + file: this.start.file, + line: this.start.line, + col: this.start.col + }); + } } return this; }); @@ -2600,9 +2681,34 @@ merge(Compressor.prototype, { if (compressor.option("unsafe")) { var expr = this.expression; if (is_undeclared_ref(expr) && global_pure_fns(expr.name)) return true; + if (expr instanceof AST_Dot + && is_undeclared_ref(expr.expression) + && (static_fns[expr.expression.name] || return_false)(expr.property)) { + return true; + } } return this.pure || !compressor.pure_funcs(this); }); + AST_Node.DEFMETHOD("is_call_pure", return_false); + AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { + if (!compressor.option("unsafe")) return; + var expr = this.expression; + var fns = return_false; + if (expr instanceof AST_Array) { + fns = native_fns.Array; + } else if (expr.is_boolean()) { + fns = native_fns.Boolean; + } else if (expr.is_number(compressor)) { + fns = native_fns.Number; + } else if (expr instanceof AST_RegExp) { + fns = native_fns.RegExp; + } else if (expr.is_string(compressor)) { + fns = native_fns.String; + } else if (!this.may_throw_on_access(compressor)) { + fns = native_fns.Object; + } + return fns(this.property); + }); // determine if expression has side effects (function(def){ @@ -2623,8 +2729,12 @@ merge(Compressor.prototype, { return any(this.body, compressor); }); def(AST_Call, function(compressor){ - return !this.is_expr_pure(compressor) - || any(this.args, compressor); + if (!this.is_expr_pure(compressor) + && (!this.expression.is_call_pure(compressor) + || this.expression.has_side_effects(compressor))) { + return true; + } + return any(this.args, compressor); }); def(AST_Switch, function(compressor){ return this.expression.has_side_effects(compressor) @@ -3480,9 +3590,7 @@ merge(Compressor.prototype, { if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) { var defs = defs_by_id[node.expression.definition().id]; if (defs) { - var key = node.property; - if (key instanceof AST_Node) key = key.getValue(); - var def = defs.get(key); + var def = defs.get(get_value(node.property)); var sym = make_node(AST_SymbolRef, node, { name: def.name, scope: node.expression.scope, @@ -3534,6 +3642,12 @@ merge(Compressor.prototype, { def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ if (!this.is_expr_pure(compressor)) { + if (this.expression.is_call_pure(compressor)) { + var exprs = this.args.slice(); + exprs.unshift(this.expression.expression); + exprs = trim(exprs, compressor, first_in_statement); + return exprs && make_sequence(this, exprs); + } if (is_func_expr(this.expression) && (!this.expression.name || !this.expression.name.definition().references.length)) { var node = this.clone(); @@ -4269,11 +4383,13 @@ merge(Compressor.prototype, { return self; } else if (exp instanceof AST_Dot) switch(exp.property) { case "toString": - if (self.args.length == 0) return make_node(AST_Binary, self, { - left: make_node(AST_String, self, { value: "" }), - operator: "+", - right: exp.expression - }).optimize(compressor); + if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) { + return make_node(AST_Binary, self, { + left: make_node(AST_String, self, { value: "" }), + operator: "+", + right: exp.expression + }).optimize(compressor); + } break; case "join": if (exp.expression instanceof AST_Array) EXIT: { @@ -4383,7 +4499,7 @@ merge(Compressor.prototype, { break; } } - if (compressor.option("unsafe_Func") + if (compressor.option("unsafe_Function") && is_undeclared_ref(exp) && exp.name == "Function") { // new Function() => function(){} @@ -5336,6 +5452,7 @@ merge(Compressor.prototype, { fixed = make_node(AST_ClassExpression, fixed, fixed); } if (fixed instanceof AST_Defun) { + fixed._squeezed = true; fixed = make_node(AST_Function, fixed, fixed); } var value; @@ -5423,7 +5540,7 @@ merge(Compressor.prototype, { } OPT(AST_Undefined, function(self, compressor){ - if (compressor.option("unsafe")) { + if (compressor.option("unsafe_undefined")) { var undef = find_variable(compressor, "undefined"); if (undef) { var ref = make_node(AST_SymbolRef, self, { @@ -5953,6 +6070,7 @@ merge(Compressor.prototype, { if (def) { return def.optimize(compressor); } + if (is_lhs(self, compressor.parent())) return self; if (compressor.option("unsafe_proto") && self.expression instanceof AST_Dot && self.expression.property == "prototype") { @@ -5991,7 +6109,6 @@ merge(Compressor.prototype, { break; } } - if (is_lhs(self, compressor.parent())) return self; var sub = self.flatten_object(self.property, compressor); if (sub) return sub.optimize(compressor); var ev = self.evaluate(compressor); diff --git a/lib/output.js b/lib/output.js index 61a25534..75f48e96 100644 --- a/lib/output.js +++ b/lib/output.js @@ -472,6 +472,11 @@ function OutputStream(options) { return OUTPUT; }; + function has_nlb() { + var index = OUTPUT.lastIndexOf("\n"); + return /^ *$/.test(OUTPUT.slice(index + 1)); + } + function prepend_comments(node) { var self = this; var start = node.start; @@ -521,7 +526,7 @@ function OutputStream(options) { comments = comments.filter(comment_filter, node); if (comments.length == 0) return; - var last_nlb = /(^|\n) *$/.test(OUTPUT); + var last_nlb = has_nlb(); comments.forEach(function(c, i) { if (!last_nlb) { if (c.nlb) { @@ -568,7 +573,7 @@ function OutputStream(options) { print("\n"); indent(); need_newline_indented = false; - } else if (c.nlb && (i > 0 || !/(^|\n) *$/.test(OUTPUT))) { + } else if (c.nlb && (i > 0 || !has_nlb())) { print("\n"); indent(); } else if (i > 0 || !tail) { @@ -1677,11 +1682,8 @@ function OutputStream(options) { function print_property_name(key, quote, output) { if (output.option("quote_keys")) { - output.print_string(key + ""); - } else if ((typeof key == "number" - || !output.option("beautify") - && +key + "" == key) - && parseFloat(key) >= 0) { + output.print_string(key); + } else if ("" + +key == key && key >= 0) { output.print(make_num(key)); } else if (RESERVED_WORDS(key) ? !output.option("ie8") : is_identifier_string(key)) { if (quote && output.option("keep_quoted_props")) { diff --git a/lib/parse.js b/lib/parse.js index 9387ad9c..d8cc9ac4 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1276,12 +1276,10 @@ function parse($TEXT, options) { }; function for_in(init) { - var lhs = init instanceof AST_Definitions ? init.definitions[0].name : null; var obj = expression(true); expect(")"); return new AST_ForIn({ init : init, - name : lhs, object : obj, body : in_loop(statement) }); @@ -2226,7 +2224,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectKeyVal({ start: start, quote: start.quote, - key: name, + key: name instanceof AST_Node ? name : "" + name, value: value, end: prev() })); @@ -2283,7 +2281,7 @@ function parse($TEXT, options) { if (typeof name === "string" || typeof name === "number") { return new AST_SymbolMethod({ start: token, - name: name, + name: "" + name, end: prev() }); } else if (name === null) { diff --git a/lib/utils.js b/lib/utils.js index dab7f566..9121fa93 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -303,6 +303,13 @@ Dictionary.prototype = { ret.push(f(this._values[i], i.substr(1))); return ret; }, + clone: function() { + var ret = new Dictionary(); + for (var i in this._values) + ret._values[i] = this._values[i]; + ret._size = this._size; + return ret; + }, toObject: function() { return this._values } }; Dictionary.fromObject = function(obj) { diff --git a/package.json b/package.json index d040201d..be6cbf19 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.3.7", + "version": "3.3.8", "engines": { "node": ">=0.8.0" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 7b5cca9e..c8acc0c0 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -4314,3 +4314,103 @@ replace_all_var: { } expect_stdout: "PASS" } + +cascade_statement: { + options = { + collapse_vars: true, + } + input: { + function f1(a, b) { + var c; + if (a) + return c = b, c || a; + else + c = a, c(b); + } + function f2(a, b) { + var c; + while (a) + c = b, a = c + b; + do + throw c = a + b, c; + while (c); + } + function f3(a, b) { + for (; a < b; a++) + if (c = a, c && b) + var c = (c = b(a), c); + } + } + expect: { + function f1(a, b) { + var c; + if (a) + return (c = b) || a; + else + (c = a)(b); + } + function f2(a, b) { + var c; + while (a) + a = (c = b) + b; + do + throw c = a + b; + while (c); + } + function f3(a, b) { + for (; a < b; a++) + if ((c = a) && b) + var c = c = b(a); + } + } +} + +cascade_forin: { + options = { + collapse_vars: true, + } + input: { + var a; + function f(b) { + return [ b, b, b ]; + } + for (var c in a = console, f(a)) + console.log(c); + } + expect: { + var a; + function f(b) { + return [ b, b, b ]; + } + for (var c in f(a = console)) + console.log(c); + } + expect_stdout: [ + "0", + "1", + "2", + ] +} + +unsafe_builtin: { + options = { + collapse_vars: true, + pure_getters: "strict", + unsafe: true, + unused: true, + } + input: { + function f(a) { + var b = Math.abs(a); + return Math.pow(b, 2); + } + console.log(f(-1), f(2)); + } + expect: { + function f(a) { + return Math.pow(Math.abs(a), 2); + } + console.log(f(-1), f(2)); + } + expect_stdout: "1 4" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index 17aadba7..dac3818d 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -1157,3 +1157,20 @@ issue_2749: { } expect_stdout: "PASS" } + +unsafe_builtin: { + options = { + side_effects: true, + unsafe: true, + } + input: { + (!w).constructor(x); + Math.abs(y); + [ 1, 2, z ].valueOf(); + } + expect: { + w, x; + y; + z; + } +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 6f5a5428..0230de10 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1287,6 +1287,9 @@ issue_2231_1: { console.log(Object.keys(void 0)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.keys(void 0) [test/compress/evaluate.js:1191,20]", + ] } issue_2231_2: { @@ -1301,6 +1304,23 @@ issue_2231_2: { console.log(Object.getOwnPropertyNames(null)); } expect_stdout: true + expect_warnings: [ + "WARN: Error evaluating Object.getOwnPropertyNames(null) [test/compress/evaluate.js:1208,20]", + ] +} + +issue_2231_3: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log(Object.keys({ foo: "bar" })[0]); + } + expect: { + console.log("foo"); + } + expect_stdout: "foo" } self_comparison_1: { @@ -1433,3 +1453,17 @@ issue_2535_3: { "WARN: Condition left of || always true [test/compress/evaluate.js:1414,20]", ] } + +issue_2822: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log([ function() {}, "PASS", "FAIL" ][1]); + } + expect: { + console.log("PASS"); + } + expect_stdout: "PASS" +} diff --git a/test/compress/functions.js b/test/compress/functions.js index f7a1cf94..ac1387a6 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -258,7 +258,7 @@ issue_203: { options = { keep_fargs: false, side_effects: true, - unsafe_Func: true, + unsafe_Function: true, unused: true, } input: { @@ -2117,3 +2117,41 @@ issue_2737_2: { } expect_stdout: "PASS" } + +issue_2783: { + options = { + collapse_vars: true, + conditionals: true, + if_return: true, + inline: true, + reduce_vars: true, + unused: true, + } + input: { + (function() { + return g; + function f(a) { + var b = a.b; + if (b) return b; + return a; + } + function g(o, i) { + while (i--) { + console.log(f(o)); + } + } + })()({ b: "PASS" }, 1); + } + expect: { + (function() { + return function(o,i) { + while (i--) console.log(f(o)); + }; + function f(a) { + var b = a.b; + return b || a; + } + })()({ b: "PASS" },1); + } + expect_stdout: "PASS" +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index 0e05a8b5..c03db107 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -897,3 +897,25 @@ toplevel_var: { } expect_stdout: "3" } + +undefined_key: { + options = { + evaluate: true, + hoist_props: true, + join_vars: true, + passes: 4, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a, o = {}; + o[a] = 1; + o.b = 2; + console.log(o[a] + o.b); + } + expect: { + console.log(3); + } + expect_stdout: "3" +} diff --git a/test/compress/issue-1443.js b/test/compress/issue-1443.js index 304a71ac..18554ff6 100644 --- a/test/compress/issue-1443.js +++ b/test/compress/issue-1443.js @@ -4,7 +4,7 @@ unsafe_undefined: { options = { conditionals: true, if_return: true, - unsafe: true + unsafe_undefined: true, } mangle = {} input: { @@ -30,7 +30,7 @@ keep_fnames: { options = { conditionals: true, if_return: true, - unsafe: true + unsafe_undefined: true, } mangle = { keep_fnames: true diff --git a/test/compress/issue-1588.js b/test/compress/issue-1588.js index 4e20a21d..05a85d40 100644 --- a/test/compress/issue-1588.js +++ b/test/compress/issue-1588.js @@ -61,7 +61,7 @@ unsafe_undefined: { options = { conditionals: true, if_return: true, - unsafe: true, + unsafe_undefined: true, } mangle = {} input: { diff --git a/test/compress/issue-1770.js b/test/compress/issue-1770.js index 953e6fcd..ed14ee05 100644 --- a/test/compress/issue-1770.js +++ b/test/compress/issue-1770.js @@ -84,12 +84,12 @@ numeric_literal: { ' 0: 0,', ' "-0": 1,', ' 42: 2,', - ' "42": 3,', + ' 42: 3,', ' 37: 4,', ' o: 5,', ' 1e42: 6,', ' b: 7,', - ' "1e+42": 8', + ' 1e42: 8', '};', '', 'console.log(obj[-0], obj[-""], obj["-0"]);', diff --git a/test/compress/properties.js b/test/compress/properties.js index 9144d9c7..bb94d4d2 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -585,6 +585,25 @@ native_prototype: { } } +native_prototype_lhs: { + options = { + unsafe_proto: true, + } + input: { + console.log(function() { + Function.prototype.bar = "PASS"; + return function() {}; + }().bar); + } + expect: { + console.log(function() { + Function.prototype.bar = "PASS"; + return function() {}; + }().bar); + } + expect_stdout: "PASS" +} + accessor_boolean: { input: { var a = 1; @@ -1466,7 +1485,7 @@ join_object_assignments_3: { expect_stdout: "PASS" } -join_object_assignments_4: { +join_object_assignments_return_1: { options = { join_vars: true, } @@ -1490,7 +1509,7 @@ join_object_assignments_4: { expect_stdout: "foo" } -join_object_assignments_5: { +join_object_assignments_return_2: { options = { join_vars: true, } @@ -1516,7 +1535,7 @@ join_object_assignments_5: { expect_stdout: "bar" } -join_object_assignments_6: { +join_object_assignments_return_3: { options = { join_vars: true, } @@ -1548,7 +1567,7 @@ join_object_assignments_6: { ] } -join_object_assignments_7: { +join_object_assignments_for: { options = { join_vars: true, } @@ -1575,3 +1594,274 @@ join_object_assignments_7: { "3", ] } + +join_object_assignments_if: { + options = { + join_vars: true, + } + input: { + console.log(function() { + var o = {}; + if (o.a = "PASS") return o.a; + }()) + } + expect: { + console.log(function() { + var o = { a: "PASS" }; + if (o.a) return o.a; + }()); + } + expect_stdout: "PASS" +} + +join_object_assignments_forin: { + options = { + join_vars: true, + } + input: { + console.log(function() { + var o = {}; + for (var a in o.a = "PASS", o) + return o[a]; + }()) + } + expect: { + console.log(function() { + var o = { a: "PASS" }; + for (var a in o) + return o[a]; + }()); + } + expect_stdout: "PASS" +} + +join_object_assignments_negative: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[0] = 0; + o[-0] = 1; + o[-1] = 2; + console.log(o[0], o[-0], o[-1]); + } + expect: { + var o = { + 0: 0, + 0: 1, + "-1": 2 + }; + console.log(o[0], o[-0], o[-1]); + } + expect_stdout: "1 1 2" +} + +join_object_assignments_NaN_1: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect_stdout: "2 2" +} + +join_object_assignments_NaN_2: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[NaN] = 1; + o[0/0] = 2; + console.log(o[NaN], o[NaN]); + } + expect: { + var o = { + NaN: 1, + NaN: 2 + }; + console.log(o.NaN, o.NaN); + } + expect_stdout: "2 2" +} + +join_object_assignments_null_0: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect_stdout: "1" +} + +join_object_assignments_null_1: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[null] = 1; + console.log(o[null]); + } + expect: { + var o = { + null: 1 + }; + console.log(o.null); + } + expect_stdout: "1" +} + +join_object_assignments_void_0: { + options = { + evaluate: true, + join_vars: true, + } + input: { + var o = {}; + o[void 0] = 1; + console.log(o[void 0]); + } + expect: { + var o = { + undefined: 1 + }; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_undefined_1: { + options = { + join_vars: true, + } + input: { + var o = {}; + o[undefined] = 1; + console.log(o[undefined]); + } + expect: { + var o = {}; + o[void 0] = 1; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_undefined_2: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[undefined] = 1; + console.log(o[undefined]); + } + expect: { + var o = { + undefined : 1 + }; + console.log(o[void 0]); + } + expect_stdout: "1" +} + +join_object_assignments_Infinity: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[Infinity] = 1; + o[1/0] = 2; + o[-Infinity] = 3; + o[-1/0] = 4; + console.log(o[Infinity], o[1/0], o[-Infinity], o[-1/0]); + } + expect: { + var o = { + Infinity: 1, + Infinity: 2, + "-Infinity": 3, + "-Infinity": 4 + }; + console.log(o[1/0], o[1/0], o[-1/0], o[-1/0]); + } + expect_stdout: "2 2 4 4" +} + +join_object_assignments_regex: { + options = { + evaluate: true, + join_vars: true, + properties: true, + } + input: { + var o = {}; + o[/rx/] = 1; + console.log(o[/rx/]); + } + expect: { + var o = { + "/rx/": 1 + }; + console.log(o[/rx/]); + } + expect_stdout: "1" +} + +issue_2816: { + options = { + join_vars: true, + } + input: { + "use strict"; + var o = { + a: 1 + }; + o.b = 2; + o.a = 3; + o.c = 4; + console.log(o.a, o.b, o.c); + } + expect: { + "use strict"; + var o = { + a: 1, + b: 2 + }; + o.a = 3; + o.c = 4; + console.log(o.a, o.b, o.c); + } + expect_stdout: "3 2 4" +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 1d94e989..5194f242 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -5892,3 +5892,61 @@ issue_2774: { } expect_stdout: "undefined" } + +issue_2799_1: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(function() { + return f; + function f(n) { + function g(i) { + return i && i + g(i - 1); + } + function h(j) { + return g(j); + } + return h(n); + } + }()(5)); + } + expect: { + console.log(function() { + return function(n) { + return function(j) { + return function g(i) { + return i && i + g(i - 1); + }(j); + }(n); + } + }()(5)); + } + expect_stdout: "15" +} + +issue_2799_2: { + options = { + reduce_vars: true, + unsafe_proto: true, + unused: true, + } + input: { + (function() { + function foo() { + Function.prototype.call.apply(console.log, [ null, "PASS" ]); + } + foo(); + })(); + } + expect: { + (function() { + (function() { + (function() {}).call.apply(console.log, [ null, "PASS" ]); + })(); + })(); + } + expect_stdout: "PASS" +} diff --git a/test/compress/sequences.js b/test/compress/sequences.js index 8ecb8b13..0584af9b 100644 --- a/test/compress/sequences.js +++ b/test/compress/sequences.js @@ -288,7 +288,7 @@ unsafe_undefined: { if_return: true, sequences: true, side_effects: true, - unsafe: true, + unsafe_undefined: true, } input: { function f(undefined) { @@ -883,3 +883,21 @@ for_init_var: { } expect_stdout: "PASS" } + +forin: { + options = { + sequences: true, + } + input: { + var o = []; + o.push("PASS"); + for (var a in o) + console.log(o[a]); + } + expect: { + var o = []; + for (var a in o.push("PASS"), o) + console.log(o[a]); + } + expect_stdout: "PASS" +} diff --git a/test/input/rename/input.js b/test/input/rename/input.js new file mode 100644 index 00000000..ef6daed2 --- /dev/null +++ b/test/input/rename/input.js @@ -0,0 +1,6 @@ +function f(x) { + return g(x); + function g(x) { + return x; + } +} diff --git a/test/mocha.js b/test/mocha.js index 411f52c5..fb8c3841 100644 --- a/test/mocha.js +++ b/test/mocha.js @@ -1,29 +1,24 @@ -var Mocha = require('mocha'), - fs = require('fs'), - path = require('path'); +var fs = require("fs"); +var Mocha = require("mocha"); +var path = require("path"); -// Instantiate a Mocha instance. -var mocha = new Mocha({}); +// Instantiate a Mocha instance +var mocha = new Mocha({ + timeout: 5000 +}); +var testDir = __dirname + "/mocha/"; -var testDir = __dirname + '/mocha/'; - -// Add each .js file to the mocha instance -fs.readdirSync(testDir).filter(function(file){ - // Only keep the .js files - return file.substr(-3) === '.js'; - -}).forEach(function(file){ - mocha.addFile( - path.join(testDir, file) - ); +// Add each .js file to the Mocha instance +fs.readdirSync(testDir).filter(function(file) { + return /\.js$/.test(file); +}).forEach(function(file) { + mocha.addFile(path.join(testDir, file)); }); module.exports = function() { mocha.run(function(failures) { - if (failures !== 0) { - process.on('exit', function () { - process.exit(failures); - }); - } + if (failures) process.on("exit", function() { + process.exit(failures); + }); }); -}; \ No newline at end of file +}; diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 3804d823..33e111a2 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -743,4 +743,36 @@ describe("bin/uglifyjs", function () { done(); }); }); + it("Should work with explicit --rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js --rename"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(a){return b(a);function b(c){return c}}\n"); + done(); + }); + }); + it("Should work with explicit --no-rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -mc --no-rename"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(n){return function(n){return n}(n)}\n"); + done(); + }); + }); + it("Should work with implicit --rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -mc"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(n){return n}\n"); + done(); + }); + }); + it("Should work with implicit --no-rename", function(done) { + var command = uglifyjscmd + " test/input/rename/input.js -c"; + exec(command, function(err, stdout, stderr) { + if (err) throw err; + assert.strictEqual(stdout, "function f(x){return function(x){return x}(x)}\n"); + done(); + }); + }); }); diff --git a/test/sandbox.js b/test/sandbox.js index 2ce9f6e1..c660353c 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -17,24 +17,30 @@ function safe_log(arg, level) { return arg; } +function strip_func_ids(text) { + return text.toString().replace(/F[0-9]{6}N/g, "N>"); +} + var FUNC_TOSTRING = [ + "[ Array, Boolean, Error, Function, Number, Object, RegExp, String].forEach(function(f) {", + " f.toString = Function.prototype.toString;", + " f.valueOf = Function.prototype.valueOf;", + "});", "Function.prototype.toString = Function.prototype.valueOf = function() {", " 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;", + " var n = this.name;", + ' if (!/^F[0-9]{6}N$/.test(n)) {', + ' n = "F" + ++id + "N";', ].concat(Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable ? [ ' Object.defineProperty(this, "name", {', " get: function() {", - " return i;", + " return n;", " }", " });", ] : [], [ " }", - ' return "[Function: " + i + "]";', + ' return "[Function: " + n + "]";', " }", "}();", 'Object.defineProperty(Function.prototype, "valueOf", { enumerable: false });', @@ -77,7 +83,7 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1); actual = actual.message.slice(actual.message.lastIndexOf("\n") + 1); } - return expected == actual; + return strip_func_ids(expected) == strip_func_ids(actual); } : function(expected, actual) { - return typeof expected == typeof actual && expected.toString() == actual.toString(); + return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual); }; diff --git a/test/travis-ufuzz.js b/test/travis-ufuzz.js new file mode 100644 index 00000000..36d1fc18 --- /dev/null +++ b/test/travis-ufuzz.js @@ -0,0 +1,82 @@ +"use strict"; + +var child_process = require("child_process"); +var https = require("https"); +var url = require("url"); + +var period = 45 * 60 * 1000; +var wait = 2 * 60 * 1000; +var ping = 5 * 60 * 1000; +if (process.argv[2] == "run") { + var endTime = Date.now() + period; + for (var i = 0; i < 2; i++) spawn(endTime); +} else if (process.argv.length > 2) { + var token = process.argv[2]; + var branch = process.argv[3] || "v" + require("../package.json").version; + var repository = encodeURIComponent(process.argv[4] || "mishoo/UglifyJS2"); + var concurrency = process.argv[5] || 1; + (function request() { + setTimeout(request, (period + wait) / concurrency); + var options = url.parse("https://api.travis-ci.org/repo/" + repository + "/requests"); + options.method = "POST"; + options.headers = { + "Content-Type": "application/json", + "Travis-API-Version": 3, + "Authorization": "token " + token + }; + https.request(options, function(res) { + console.log("HTTP", res.statusCode); + console.log(JSON.stringify(res.headers, null, 2)); + console.log(); + res.setEncoding("utf8"); + res.on("data", console.log); + }).on("error", console.error).end(JSON.stringify({ + request: { + message: "ufuzz testing (when idle)", + branch: branch, + config: { + merge_mode: "replace", + language: "node_js", + node_js: "9", + sudo: false, + script: "node test/travis-ufuzz run" + } + } + })); + })(); +} else { + console.log("Usage: test/travis-ufuzz.js [branch] [repository] [concurrency]"); +} + +function spawn(endTime) { + var child = child_process.spawn("node", [ + "--max-old-space-size=2048", + "test/ufuzz" + ], { + stdio: [ "ignore", "pipe", "pipe" ] + }).on("exit", respawn); + var line = ""; + child.stdout.on("data", function(data) { + line += data; + }); + child.stderr.on("data", function() { + process.exitCode = 1; + }).pipe(process.stdout); + var keepAlive = setInterval(function() { + var end = line.lastIndexOf("\r"); + console.log(line.slice(line.lastIndexOf("\r", end - 1) + 1, end)); + line = line.slice(end + 1); + }, ping); + var timer = setTimeout(function() { + clearInterval(keepAlive); + child.removeListener("exit", respawn); + child.kill(); + }, endTime - Date.now()); + + function respawn() { + console.log(line); + clearInterval(keepAlive); + clearTimeout(timer); + spawn(endTime); + } +} diff --git a/test/ufuzz.js b/test/ufuzz.js index d02e9f76..bbf4fa09 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -998,10 +998,11 @@ function log_suspects(minify_options, component) { if (typeof options != "object") options = {}; var defs = default_options[component]; var suspects = Object.keys(defs).filter(function(name) { - if ((name in options ? options : defs)[name]) { + var flip = name == "keep_fargs"; + if (flip ? name in options : (name in options ? options : defs)[name]) { var m = JSON.parse(JSON.stringify(minify_options)); var o = JSON.parse(JSON.stringify(options)); - o[name] = false; + o[name] = flip; m[component] = o; var result = UglifyJS.minify(original_code, m); if (result.error) { @@ -1022,6 +1023,24 @@ function log_suspects(minify_options, component) { } } +function log_rename(options) { + if (!options.rename) return; + var m = JSON.parse(JSON.stringify(options)); + m.rename = false; + var result = UglifyJS.minify(original_code, m); + if (result.error) { + errorln("Error testing options.rename"); + errorln(result.error.stack); + } else { + var r = sandbox.run_code(result.code); + if (sandbox.same_stdout(original_result, r)) { + errorln("Suspicious options:"); + errorln(" rename"); + errorln(); + } + } +} + function log(options) { if (!ok) errorln('\n\n\n\n\n\n!!!!!!!!!!\n\n\n'); errorln("//============================================================="); @@ -1056,6 +1075,7 @@ function log(options) { errorln(); if (!ok && typeof uglify_code == "string") { Object.keys(default_options).forEach(log_suspects.bind(null, options)); + log_rename(options); errorln("!!!!!! Failed... round " + round); } } diff --git a/test/ufuzz.json b/test/ufuzz.json index 4057a351..5ccd96e0 100644 --- a/test/ufuzz.json +++ b/test/ufuzz.json @@ -5,7 +5,8 @@ "output": { "beautify": true, "bracketize": true - } + }, + "rename": true }, { "compress": false @@ -20,7 +21,13 @@ { "compress": { "keep_fargs": false, - "passes": 100 + "passes": 1e6, + "sequences": 1e6, + "unsafe": true, + "unsafe_Function": true, + "unsafe_math": true, + "unsafe_proto": true, + "unsafe_regexp": true } } ]