diff --git a/README.md b/README.md index 2541e2f968e..aeea929dd5f 100644 --- a/README.md +++ b/README.md @@ -617,17 +617,17 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u the resultant code is shorter: `m(){return x}` becomes `m:()=>x`. This transform requires that the `ecma` compress option is set to `6` or greater. -- `booleans` (default: `true`) -- various optimizations for boolean context, for example `!!a - ? b : c → a ? b : c` +- `booleans` (default: `true`) -- various optimizations for boolean context, + for example `!!a ? b : c → a ? b : c` -- `cascade` (default: `true`) -- small optimization for sequences, transform `x, x` into `x` - and `x = something(), x` into `x = something()` +- `cascade` (default: `true`) -- small optimization for sequences, transform + `x, x` into `x` and `x = something(), x` into `x = something()` -- `collapse_vars` (default: `true`) -- Collapse single-use non-constant variables - side - effects permitting. +- `collapse_vars` (default: `true`) -- Collapse single-use non-constant variables, + side effects permitting. -- `comparisons` (default: `true`) -- apply certain optimizations to binary nodes, for example: - `!(a <= b) → a > b` (only when `unsafe_comps`), attempts to negate binary +- `comparisons` (default: `true`) -- apply certain optimizations to binary nodes, + e.g. `!(a <= b) → a > b` (only when `unsafe_comps`), attempts to negate binary nodes, e.g. `a = !b && !c && !d && !e → a=!(b||c||d||e)` etc. - `computed_props` (default: `true`) -- Transforms constant computed properties @@ -638,7 +638,7 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `dead_code` (default: `true`) -- remove unreachable code -- `drop_console` (default: `false`) -- default `false`. Pass `true` to discard calls to +- `drop_console` (default: `false`) -- Pass `true` to discard calls to `console.*` functions. If you wish to drop a specific function call such as `console.info` and/or retain side effects from function arguments after dropping the function call then use `pure_funcs` instead. @@ -650,14 +650,14 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `evaluate` (default: `true`) -- attempt to evaluate constant expressions -- `expression` (default: `false`) -- default `false`. Pass `true` to preserve completion values +- `expression` (default: `false`) -- Pass `true` to preserve completion values from terminal statements without `return`, e.g. in bookmarklets. - `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation) - `hoist_funs` (default: `true`) -- hoist function declarations -- `hoist_props` (default: `false`) -- hoist properties from constant object and +- `hoist_props` (default: `true`) -- hoist properties from constant object and array literals into regular variables subject to a set of constraints. For example: `var o={p:1, q:2}; f(o.p, o.q);` is converted to `f(1, 2);`. Note: `hoist_props` works best with `mangle` enabled, the `compress` option `passes` set to `2` or higher, @@ -676,19 +676,18 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u compressor from discarding class names. See also: the `keep_classnames` [mangle option](#mangle). -- `keep_fargs` (default: `true`) -- default `true`. Prevents the - compressor from discarding unused function arguments. You need this - for code which relies on `Function.length`. +- `keep_fargs` (default: `true`) -- Prevents the compressor from discarding unused + function arguments. You need this for code which relies on `Function.length`. - `keep_fnames` (default: `false`) -- Pass `true` to prevent the compressor from discarding function names. Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). -- `keep_infinity` (default: `false`) -- default `false`. Pass `true` to prevent `Infinity` from +- `keep_infinity` (default: `false`) -- Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. -- `loops` (default: `true`) -- optimizations for `do`, `while` and `for` loops when we can - statically determine the condition +- `loops` (default: `true`) -- optimizations for `do`, `while` and `for` loops + when we can statically determine the condition. - `negate_iife` (default: `true`) -- negate "Immediately-Called Function Expressions" where the return value is discarded, to avoid the parens that the @@ -719,11 +718,11 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u `foo` is certain to not throw, i.e. not `null` or `undefined`. - `reduce_funcs` (default: `true`) -- Allows single-use functions to be - inlined as function expressions when permissible allowing further - optimization. Enabled by default. Option depends on `reduce_vars` - being enabled. Some code runs faster in the Chrome V8 engine if this + inlined as function expressions when permissible allowing further + optimization. Enabled by default. Option depends on `reduce_vars` + being enabled. Some code runs faster in the Chrome V8 engine if this option is disabled. Does not negatively impact other major browsers. - + - `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and used as constant values. @@ -736,21 +735,22 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u occasions the default sequences limit leads to very slow compress times in which case a value of `20` or less is recommended. -- `side_effects` (default: `true`) -- default `true`. Pass `false` to disable potentially dropping +- `side_effects` (default: `true`) -- Pass `false` to disable potentially dropping functions marked as "pure". A function call is marked as "pure" if a comment annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: `/*@__PURE__*/foo();` - `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches -- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) - in the top level scope (`false` by default, `true` to drop both unreferenced - functions and variables) +- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or + variables (`"vars"`) in the top level scope (`false` by default, `true` to drop + both unreferenced functions and variables) -- `top_retain` (default: `null`) -- prevent specific toplevel functions and variables from `unused` - removal (can be array, comma-separated, RegExp or function. Implies `toplevel`) +- `top_retain` (default: `null`) -- prevent specific toplevel functions and + variables from `unused` removal (can be array, comma-separated, RegExp or + function. Implies `toplevel`) -- `typeofs` (default: `true`) -- default `true`. Transforms `typeof foo == "undefined"` into +- `typeofs` (default: `true`) -- Transforms `typeof foo == "undefined"` into `foo === void 0`. Note: recommend to set this value to `false` for IE10 and earlier versions due to known issues. @@ -788,31 +788,31 @@ 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. -- `unused` (default: `true`) -- drop unreferenced functions and variables (simple direct variable - assignments do not count as references unless set to `"keep_assign"`) +- `unused` (default: `true`) -- drop unreferenced functions and variables (simple + direct variable assignments do not count as references unless set to `"keep_assign"`) -- `warnings` (default: `false`) -- display warnings when dropping unreachable code or unused - declarations etc. +- `warnings` (default: `false`) -- display warnings when dropping unreachable + code or unused declarations etc. ## Mangle options -- `eval` (default `false`). Pass `true` to mangle names visible in scopes +- `eval` (default `false`) -- Pass `true` to mangle names visible in scopes where `eval` or `with` are used. -- `keep_classnames` (default `false`). Pass `true` to not mangle class names. +- `keep_classnames` (default `false`) -- Pass `true` to not mangle class names. See also: the `keep_classnames` [compress option](#compress-options). -- `keep_fnames` (default `false`). Pass `true` to not mangle function names. +- `keep_fnames` (default `false`) -- Pass `true` to not mangle function names. Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` [compress option](#compress-options). -- `reserved` (default `[]`). Pass an array of identifiers that should be +- `reserved` (default `[]`) -- Pass an array of identifiers that should be excluded from mangling. Example: `["foo", "bar"]`. -- `toplevel` (default `false`). Pass `true` to mangle names declared in the +- `toplevel` (default `false`) -- Pass `true` to mangle names declared in the top level scope. -- `safari10` (default `false`). Pass `true` to work around the Safari 10 loop +- `safari10` (default `false`) -- Pass `true` to work around the Safari 10 loop iterator [bug](https://bugs.webkit.org/show_bug.cgi?id=171041) "Cannot declare a let variable twice". diff --git a/bin/uglifyjs b/bin/uglifyjs index e6d5c20b9c4..867df606fe6 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -49,6 +49,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("--no-rename", "Disable symbol expansion."); program.option("--self", "Build UglifyJS as a library (implies --wrap UglifyJS)"); program.option("--source-map [options]", "Enable source map/specify source map options.", parse_source_map()); program.option("--timings", "Display operations run time on STDERR.") @@ -67,11 +68,13 @@ if (!program.output && program.sourceMap && program.sourceMap.url != "inline") { "compress", "ie8", "mangle", + "rename", "sourceMap", "toplevel", "wrap" ].forEach(function(name) { if (name in program) { + if (name == "rename" && program[name]) return; options[name] = program[name]; } }); diff --git a/lib/compress.js b/lib/compress.js index 48816ff7a93..b1f063f5e1a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -63,7 +63,7 @@ function Compressor(options, false_by_default) { expression : false, global_defs : {}, hoist_funs : !false_by_default, - hoist_props : false, + hoist_props : !false_by_default, hoist_vars : false, ie8 : false, if_return : !false_by_default, @@ -341,7 +341,7 @@ merge(Compressor.prototype, { } } } - mark_escaped(d, node, value, 0); + mark_escaped(d, node.scope, node, value, 0); } if (node instanceof AST_SymbolCatch) { node.definition().fixed = false; @@ -627,7 +627,8 @@ merge(Compressor.prototype, { || !immutable && parent instanceof AST_Call && parent.expression === node - && (!(value instanceof AST_Function) || value.contains_this(parent))) { + && (!(value instanceof AST_Function) + || !(parent instanceof AST_New) && value.contains_this())) { return true; } else if (parent instanceof AST_Array) { return is_modified(parent, parent, level + 1); @@ -639,10 +640,13 @@ merge(Compressor.prototype, { } } - function mark_escaped(d, node, value, level) { + function mark_escaped(d, scope, node, value, level) { var parent = tw.parent(level); - if (value instanceof AST_Constant || value instanceof AST_ClassExpression) return; - if (level > 0 && value instanceof AST_Function) return; + if (value) { + if (value.is_constant()) return; + if (value instanceof AST_ClassExpression) return; + if (level > 0 && value.is_constant_expression(scope)) return; + } if (parent instanceof AST_Assign && parent.operator == "=" && node === parent.right || parent instanceof AST_Call && node !== parent.expression || parent instanceof AST_Return && node === parent.value && node.scope !== d.scope @@ -650,13 +654,13 @@ merge(Compressor.prototype, { d.escaped = true; return; } else if (parent instanceof AST_Array) { - mark_escaped(d, parent, parent, level + 1); + mark_escaped(d, scope, parent, parent, level + 1); } else if (parent instanceof AST_ObjectKeyVal && node === parent.value) { var obj = tw.parent(level + 1); - mark_escaped(d, obj, obj, level + 2); + mark_escaped(d, scope, obj, obj, level + 2); } else if (parent instanceof AST_PropAccess && node === parent.expression) { value = read_property(value, parent.property); - mark_escaped(d, parent, value, level + 1); + mark_escaped(d, scope, parent, value, level + 1); if (value) return; } if (level == 0) d.direct_access = true; @@ -845,6 +849,12 @@ merge(Compressor.prototype, { } } + function is_identifier_atom(node) { + return node instanceof AST_Infinity + || node instanceof AST_NaN + || node instanceof AST_Undefined; + } + function tighten_body(statements, compressor) { var CHANGED, max_iter = 10; do { @@ -911,7 +921,7 @@ merge(Compressor.prototype, { && !(node instanceof AST_SymbolDeclaration) && lhs.equivalent_to(node)) { if (is_lhs(node, parent)) { - if (candidate.multiple) replaced++; + if (value_def) replaced++; return node; } CHANGED = abort = true; @@ -926,19 +936,24 @@ merge(Compressor.prototype, { return make_node(AST_UnaryPrefix, candidate, candidate); } if (candidate instanceof AST_VarDef) { - if (candidate.multiple) { + if (value_def) { abort = false; return node; } var def = candidate.name.definition(); + var value = candidate.value; if (def.references.length - def.replaced == 1 && !compressor.exposed(def)) { def.replaced++; - return maintain_this_binding(parent, node, candidate.value); + if (funarg && is_identifier_atom(value)) { + return value.transform(compressor); + } else { + return maintain_this_binding(parent, node, value); + } } return make_node(AST_Assign, candidate, { operator: "=", left: make_node(AST_SymbolRef, candidate.name, candidate.name), - right: candidate.value + right: value }); } candidate.write_only = false; @@ -1000,18 +1015,20 @@ merge(Compressor.prototype, { extract_candidates(statements[stat_index]); while (candidates.length > 0) { var candidate = candidates.pop(); + var value_def = null; var lhs = get_lhs(candidate); if (!lhs || is_lhs_read_only(lhs) || lhs.has_side_effects(compressor)) continue; // Locate symbols which may execute code outside of scanning range var lvalues = get_lvalues(candidate); if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; - var replace_all = candidate.multiple; + var replace_all = value_def; if (!replace_all && lhs instanceof AST_SymbolRef) { var def = lhs.definition(); replace_all = def.references.length - def.replaced == 1; } var side_effects = value_has_side_effects(candidate); - var hit = candidate.name instanceof AST_SymbolFunarg; + var funarg = candidate.name instanceof AST_SymbolFunarg; + var hit = funarg; var abort = false, replaced = 0, can_replace = !args || !hit; if (!can_replace) { for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) { @@ -1022,13 +1039,12 @@ merge(Compressor.prototype, { for (var i = stat_index; !abort && i < statements.length; i++) { statements[i].transform(scanner); } - if (candidate.multiple) { + if (value_def) { var def = candidate.name.definition(); if (abort && def.references.length - def.replaced > replaced) replaced = false; else { abort = false; - hit = candidate.name instanceof AST_SymbolFunarg; - var value_def = candidate.value.definition(); + hit = funarg; for (var i = stat_index; !abort && i < statements.length; i++) { statements[i].transform(multi_replacer); } @@ -1131,13 +1147,13 @@ merge(Compressor.prototype, { } } - function mangleable_var(expr) { - var value = expr.value; - if (!(value instanceof AST_SymbolRef)) return false; - if (value.name == "arguments") return false; - if (value.definition().undeclared) return false; - expr.multiple = true; - return true; + function mangleable_var(var_def) { + var value = var_def.value; + if (!(value instanceof AST_SymbolRef)) return; + if (value.name == "arguments") return; + var def = value.definition(); + if (def.undeclared) return; + return value_def = def; } function get_lhs(expr) { @@ -4062,9 +4078,7 @@ merge(Compressor.prototype, { if (self.operator == "delete" && !(e instanceof AST_SymbolRef || e instanceof AST_PropAccess - || e instanceof AST_NaN - || e instanceof AST_Infinity - || e instanceof AST_Undefined)) { + || is_identifier_atom(e))) { if (e instanceof AST_Sequence) { e = e.expressions.slice(); e.push(make_node(AST_True, self)); @@ -4972,6 +4986,17 @@ merge(Compressor.prototype, { if (compressor.option("properties")) { var key = prop.evaluate(compressor); if (key !== prop) { + if (typeof key == "string") { + if (key == "undefined") { + key = undefined; + } else { + var value = parseFloat(key); + if (value.toString() == key) { + key = value; + } + } + } + prop = self.property = best_of_expression(prop, make_node_from_constant(key, prop).transform(compressor)); var property = "" + key; if (is_identifier_string(property) && property.length <= prop.print_to_string().length + 1) { @@ -4980,14 +5005,6 @@ merge(Compressor.prototype, { property: property }).optimize(compressor); } - if (!(prop instanceof AST_Number)) { - var value = parseFloat(property); - if (value.toString() == property) { - prop = self.property = make_node(AST_Number, prop, { - value: value - }); - } - } } } if (is_lhs(self, compressor.parent())) return self; @@ -5044,8 +5061,7 @@ merge(Compressor.prototype, { return self; }); - AST_Lambda.DEFMETHOD("contains_this", function(grandparent) { - if (grandparent instanceof AST_New) return false; + AST_Lambda.DEFMETHOD("contains_this", function() { var result; var self = this; self.walk(new TreeWalker(function(node) { @@ -5071,7 +5087,8 @@ merge(Compressor.prototype, { })) break; var value = prop.value; if ((value instanceof AST_Accessor || value instanceof AST_Function) - && value.contains_this(compressor.parent())) break; + && !(compressor.parent() instanceof AST_New) + && value.contains_this()) break; return make_node(AST_Sub, this, { expression: make_node(AST_Array, expr, { elements: props.map(function(prop) { diff --git a/lib/minify.js b/lib/minify.js index 2ac6afd642f..0fd6e1dc2c7 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -57,6 +57,7 @@ function minify(files, options) { nameCache: null, output: {}, parse: {}, + rename: undefined, sourceMap: false, timings: false, toplevel: false, @@ -69,6 +70,9 @@ function minify(files, options) { if (options.keep_classnames === undefined) { options.keep_classnames = options.keep_fnames; } + if (options.rename === undefined) { + options.rename = options.compress && options.mangle; + } set_shorthand("ecma", options, [ "parse", "compress", "output" ]); set_shorthand("ie8", options, [ "compress", "mangle", "output" ]); set_shorthand("keep_classnames", options, [ "compress", "mangle" ]); @@ -146,6 +150,11 @@ function minify(files, options) { if (options.wrap) { toplevel = toplevel.wrap_commonjs(options.wrap); } + if (timings) timings.rename = Date.now(); + if (options.rename) { + toplevel.figure_out_scope(options.mangle); + toplevel.expand_names(options.mangle); + } if (timings) timings.compress = Date.now(); if (options.compress) toplevel = new Compressor(options.compress).compress(toplevel); if (timings) timings.scope = Date.now(); @@ -206,7 +215,8 @@ function minify(files, options) { if (timings) { timings.end = Date.now(); result.timings = { - parse: 1e-3 * (timings.compress - timings.parse), + parse: 1e-3 * (timings.rename - timings.parse), + rename: 1e-3 * (timings.compress - timings.rename), compress: 1e-3 * (timings.scope - timings.compress), scope: 1e-3 * (timings.mangle - timings.scope), mangle: 1e-3 * (timings.properties - timings.mangle), diff --git a/lib/scope.js b/lib/scope.js index 224082024db..40ab844500e 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -43,7 +43,7 @@ "use strict"; -function SymbolDef(scope, index, orig) { +function SymbolDef(scope, orig) { this.name = orig.name; this.orig = [ orig ]; this.eliminated = 0; @@ -54,7 +54,6 @@ function SymbolDef(scope, index, orig) { this.export = false; this.mangled_name = null; this.undeclared = false; - this.index = index; this.id = SymbolDef.next_id++; }; @@ -346,7 +345,7 @@ AST_Toplevel.DEFMETHOD("def_global", function(node){ if (globals.has(name)) { return globals.get(name); } else { - var g = new SymbolDef(this, globals.size(), node); + var g = new SymbolDef(this, node); g.undeclared = true; g.global = true; globals.set(name, g); @@ -417,7 +416,7 @@ AST_Scope.DEFMETHOD("def_function", function(symbol){ AST_Scope.DEFMETHOD("def_variable", function(symbol){ var def; if (!this.variables.has(symbol.name)) { - def = new SymbolDef(this, this.variables.size(), symbol); + def = new SymbolDef(this, symbol); this.variables.set(symbol.name, def); def.global = !this.parent_scope; } else { @@ -435,7 +434,7 @@ AST_Scope.DEFMETHOD("next_mangled", function(options){ // https://github.com/mishoo/UglifyJS2/issues/242 -- do not // shadow a name reserved from mangling. - if (options.reserved.indexOf(m) >= 0) continue; + if (member(m, options.reserved)) continue; // we must ensure that the mangled name does not shadow a name // from some parent scope that is referenced in this or in @@ -487,7 +486,7 @@ AST_Symbol.DEFMETHOD("global", function(){ return this.definition().global; }); -AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ +AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options) { options = defaults(options, { eval : false, ie8 : false, @@ -497,15 +496,14 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ toplevel : false, }); if (!Array.isArray(options.reserved)) options.reserved = []; + // Never mangle arguments + push_uniq(options.reserved, "arguments"); return options; }); AST_Toplevel.DEFMETHOD("mangle_names", function(options){ options = this._default_mangler_options(options); - // Never mangle arguments - options.reserved.push('arguments'); - // We only need to mangle declaration nodes. Special logic wired // into the code generator will display the mangled name if it's // present (and for AST_SymbolRef-s it'll use the mangled name of @@ -514,11 +512,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ var to_mangle = []; if (options.cache) { - this.globals.each(function(symbol){ - if (options.reserved.indexOf(symbol.name) < 0) { - to_mangle.push(symbol); - } - }); + this.globals.each(collect); } var tw = new TreeWalker(function(node, descend){ @@ -530,13 +524,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ return true; // don't descend again in TreeWalker } if (node instanceof AST_Scope) { - var p = tw.parent(), a = []; - node.variables.each(function(symbol){ - if (options.reserved.indexOf(symbol.name) < 0) { - a.push(symbol); - } - }); - to_mangle.push.apply(to_mangle, a); + node.variables.each(collect); return; } if (node instanceof AST_Label) { @@ -561,6 +549,74 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ if (options.cache) { options.cache.cname = this.cname; } + + function collect(symbol) { + if (!member(symbol.name, options.reserved)) { + to_mangle.push(symbol); + } + } +}); + +AST_Toplevel.DEFMETHOD("find_unique_prefix", function(options) { + var letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_"; + var cache = options.cache && options.cache.props; + var prefixes = Object.create(null); + options.reserved.forEach(add_prefix); + this.globals.each(add_def); + this.walk(new TreeWalker(function(node) { + if (node instanceof AST_Scope) node.variables.each(add_def); + if (node instanceof AST_SymbolCatch) add_def(node.definition()); + })); + var prefix, i = 0; + do { + prefix = create_name(i++); + } while (prefixes[prefix]); + return prefix; + + function add_prefix(name) { + if (/[0-9]$/.test(name)) { + prefixes[name.replace(/[0-9]+$/, "")] = true; + } + } + + function add_def(def) { + var name = def.name; + if (def.global && cache && cache.has(name)) name = cache.get(name); + else if (!def.unmangleable(options)) return; + add_prefix(name); + } + + function create_name(num) { + var name = ""; + do { + name += letters[num % letters.length]; + num = Math.floor(num / letters.length); + } while (num); + return name; + } +}); + +AST_Toplevel.DEFMETHOD("expand_names", function(options) { + options = this._default_mangler_options(options); + var prefix = this.find_unique_prefix(options); + this.globals.each(rename); + this.walk(new TreeWalker(function(node) { + if (node instanceof AST_Scope) node.variables.each(rename); + if (node instanceof AST_SymbolCatch) rename(node.definition()); + })); + + function rename(def) { + if (def.global || def.unmangleable(options)) return; + if (member(def.name, options.reserved)) return; + var d = def.redefined(); + def.name = d ? d.name : prefix + def.id; + def.orig.forEach(function(sym) { + sym.name = def.name; + }); + def.references.forEach(function(sym) { + sym.name = def.name; + }); + } }); AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ diff --git a/package.json b/package.json index 385d866d7db..6599d355c23 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.1.10", + "version": "3.2.0", "engines": { "node": ">=0.8.0" }, @@ -26,11 +26,11 @@ "LICENSE" ], "dependencies": { - "commander": "~2.11.0", + "commander": "~2.12.1", "source-map": "~0.6.1" }, "devDependencies": { - "acorn": "~5.1.1", + "acorn": "~5.2.1", "mocha": "~3.5.1", "semver": "~5.4.1" }, diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 433ae9f2c2e..d44f5910aa4 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3880,3 +3880,81 @@ issue_2436_14: { } expect_stdout: true } + +issue_2497: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function sample() { + if (true) { + for (var i = 0; i < 1; ++i) { + for (var k = 0; k < 1; ++k) { + var value = 1; + var x = value; + value = x ? x + 1 : 0; + } + } + } else { + for (var i = 0; i < 1; ++i) { + for (var k = 0; k < 1; ++k) { + var value = 1; + } + } + } + } + } + expect: { + function sample() { + if (true) + for (i = 0; i < 1; ++i) + for (k = 0; k < 1; ++k) { + value = 1; + value = value ? value + 1 : 0; + } + else + for (var i = 0; i < 1; ++i) + for (var k = 0; k < 1; ++k) + var value=1; + } + } +} + +issue_2506: { + options = { + collapse_vars: true, + reduce_vars: true, + unused: true, + } + input: { + var c = 0; + function f0(bar) { + function f1(Infinity_2) { + function f13(NaN) { + if (false <= NaN & this >> 1 >= 0) { + c++; + } + } + var b_2 = f13(NaN, c++); + } + var bar = f1(-3, -1); + } + f0(false); + console.log(c); + } + expect: { + var c = 0; + function f0(bar) { + (function(Infinity_2) { + (function(NaN) { + if (false <= 0/0 & this >> 1 >= 0) + c++; + })(0, c++); + })(); + } + f0(false); + console.log(c); + } + expect_stdout: "1" +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index e5f129a4722..94c8d986884 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -639,3 +639,136 @@ issue_2473_4: { } expect_stdout: "1 2" } + +issue_2508_1: { + options = { + collapse_vars: true, + hoist_props: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: [ 1 ], + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect: { + (function(x) { + console.log(x); + })([ 1 ]); + } + expect_stdout: true +} + +issue_2508_2: { + options = { + collapse_vars: true, + hoist_props: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: { b: 2 }, + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect: { + (function(x) { + console.log(x); + })({ b: 2 }); + } + expect_stdout: true +} + +issue_2508_3: { + options = { + collapse_vars: true, + hoist_props: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: [ o ], + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect: { + var o = { + a: [ o ], + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect_stdout: true +} + +issue_2508_4: { + options = { + collapse_vars: true, + hoist_props: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + a: { b: o }, + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect: { + var o = { + a: { b: o }, + f: function(x) { + console.log(x); + } + }; + o.f(o.a); + } + expect_stdout: true +} + +issue_2508_5: { + options = { + collapse_vars: true, + hoist_props: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { + f: function(x) { + console.log(x); + } + }; + o.f(o.f); + } + expect: { + var o_f = function(x) { + console.log(x); + }; + o_f(o_f); + } + expect_stdout: true +} diff --git a/test/compress/properties.js b/test/compress/properties.js index e252d4956af..61d15e23de4 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1305,3 +1305,29 @@ new_this: { }(42); } } + +issue_2513: { + options = { + evaluate: true, + properties: true, + } + input: { + !function(Infinity, NaN, undefined) { + console.log("a"[1/0], "b"["Infinity"]); + console.log("c"[0/0], "d"["NaN"]); + console.log("e"[void 0], "f"["undefined"]); + }(0, 0, 0); + } + expect: { + !function(Infinity, NaN, undefined) { + console.log("a"[1/0], "b"[1/0]); + console.log("c".NaN, "d".NaN); + console.log("e"[void 0], "f"[void 0]); + }(0, 0, 0); + } + expect_stdout: [ + "undefined undefined", + "undefined undefined", + "undefined undefined", + ] +} diff --git a/test/compress/rename.js b/test/compress/rename.js new file mode 100644 index 00000000000..defc6cf5653 --- /dev/null +++ b/test/compress/rename.js @@ -0,0 +1,536 @@ +mangle_catch: { + rename = true + options = { + ie8: false, + toplevel: false, + } + mangle = { + ie8: false, + toplevel: false, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + a = "PASS"; + } + console.log(a); + } + expect_exact: 'var a="FAIL";try{throw 1}catch(o){a="PASS"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_ie8: { + rename = true + options = { + ie8: true, + toplevel: false, + } + mangle = { + ie8: true, + toplevel: false, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + a = "PASS"; + } + console.log(a); + } + expect_exact: 'var a="FAIL";try{throw 1}catch(args){a="PASS"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_var: { + rename = true + options = { + ie8: false, + toplevel: false, + } + mangle = { + ie8: false, + toplevel: false, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + var a = "PASS"; + } + console.log(a); + } + expect_exact: 'var a="FAIL";try{throw 1}catch(o){var a="PASS"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_var_ie8: { + rename = true + options = { + ie8: true, + toplevel: false, + } + mangle = { + ie8: true, + toplevel: false, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + var a = "PASS"; + } + console.log(a); + } + expect_exact: 'var a="FAIL";try{throw 1}catch(args){var a="PASS"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_toplevel: { + rename = true + options = { + ie8: false, + toplevel: true, + } + mangle = { + ie8: false, + toplevel: true, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + a = "PASS"; + } + console.log(a); + } + expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_ie8_toplevel: { + rename = true + options = { + ie8: true, + toplevel: true, + } + mangle = { + ie8: true, + toplevel: true, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + a = "PASS"; + } + console.log(a); + } + expect_exact: 'var o="FAIL";try{throw 1}catch(c){o="PASS"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_var_toplevel: { + rename = true + options = { + ie8: false, + toplevel: true, + } + mangle = { + ie8: false, + toplevel: true, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + var a = "PASS"; + } + console.log(a); + } + expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_var_ie8_toplevel: { + rename = true + options = { + ie8: true, + toplevel: true, + } + mangle = { + ie8: true, + toplevel: true, + } + input: { + var a = "FAIL"; + try { + throw 1; + } catch (args) { + var a = "PASS"; + } + console.log(a); + } + expect_exact: 'var o="FAIL";try{throw 1}catch(r){var o="PASS"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_redef_1: { + rename = true + options = { + ie8: false, + toplevel: false, + } + mangle = { + ie8: false, + toplevel: false, + } + input: { + var a = "PASS"; + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_redef_1_ie8: { + rename = true + options = { + ie8: true, + toplevel: false, + } + mangle = { + ie8: true, + toplevel: false, + } + input: { + var a = "PASS"; + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'var a="PASS";try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);' + expect_stdout: "PASS" +} + +mangle_catch_redef_1_toplevel: { + rename = true + options = { + ie8: false, + toplevel: true, + } + mangle = { + ie8: false, + toplevel: true, + } + input: { + var a = "PASS"; + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_redef_1_ie8_toplevel: { + rename = true + options = { + ie8: true, + toplevel: true, + } + mangle = { + ie8: true, + toplevel: true, + } + input: { + var a = "PASS"; + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'var o="PASS";try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);' + expect_stdout: "PASS" +} + +mangle_catch_redef_2: { + rename = true + options = { + ie8: false, + toplevel: false, + } + mangle = { + ie8: false, + toplevel: false, + } + input: { + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);' + expect_stdout: "undefined" +} + +mangle_catch_redef_2_ie8: { + rename = true + options = { + ie8: true, + toplevel: false, + } + mangle = { + ie8: true, + toplevel: false, + } + input: { + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'try{throw"FAIL1"}catch(a){var a="FAIL2"}console.log(a);' + expect_stdout: "undefined" +} + +mangle_catch_redef_2_toplevel: { + rename = true + options = { + ie8: false, + toplevel: true, + } + mangle = { + ie8: false, + toplevel: true, + } + input: { + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);' + expect_stdout: "undefined" +} + +mangle_catch_redef_2_ie8_toplevel: { + rename = true + options = { + ie8: true, + toplevel: true, + } + mangle = { + ie8: true, + toplevel: true, + } + input: { + try { + throw "FAIL1"; + } catch (a) { + var a = "FAIL2"; + } + console.log(a); + } + expect_exact: 'try{throw"FAIL1"}catch(o){var o="FAIL2"}console.log(o);' + expect_stdout: "undefined" +} + +issue_2120_1: { + rename = true + mangle = { + ie8: false, + } + input: { + "aaaaaaaa"; + var a = 1, b = "FAIL"; + try { + throw 1; + } catch (c) { + try { + throw 0; + } catch (a) { + if (c) b = "PASS"; + } + } + console.log(b); + } + expect: { + "aaaaaaaa"; + var a = 1, b = "FAIL"; + try { + throw 1; + } catch (t) { + try { + throw 0; + } catch (a) { + if (t) b = "PASS"; + } + } + console.log(b); + } + expect_stdout: "PASS" +} + +issue_2120_2: { + rename = true + mangle = { + ie8: true, + } + input: { + "aaaaaaaa"; + var a = 1, b = "FAIL"; + try { + throw 1; + } catch (c) { + try { + throw 0; + } catch (a) { + if (c) b = "PASS"; + } + } + console.log(b); + } + expect: { + "aaaaaaaa"; + var a = 1, b = "FAIL"; + try { + throw 1; + } catch (c) { + try { + throw 0; + } catch (a) { + if (c) b = "PASS"; + } + } + console.log(b); + } + expect_stdout: "PASS" +} +function_iife_catch: { + rename = true + mangle = { + ie8: false, + } + input: { + function f(n) { + !function() { + try { + throw 0; + } catch (n) { + var a = 1; + console.log(n, a); + } + }(); + } + f(); + } + expect_exact: "function f(o){!function(){try{throw 0}catch(c){var o=1;console.log(c,o)}}()}f();" + expect_stdout: "0 1" +} + +function_iife_catch_ie8: { + rename = true + mangle = { + ie8: true, + } + input: { + function f(n) { + !function() { + try { + throw 0; + } catch (n) { + var a = 1; + console.log(n, a); + } + }(); + } + f(); + } + expect_exact: "function f(o){!function(){try{throw 0}catch(o){var c=1;console.log(o,c)}}()}f();" + expect_stdout: "0 1" +} + +function_catch_catch: { + rename = true + mangle = { + ie8: false, + } + input: { + var o = 0; + function f() { + try { + throw 1; + } catch (c) { + try { + throw 2; + } catch (o) { + var o = 3; + console.log(o); + } + } + console.log(o); + } + f(); + } + expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();" + expect_stdout: [ + "3", + "undefined", + ] +} + +function_catch_catch_ie8: { + rename = true + mangle = { + ie8: true, + } + input: { + var o = 0; + function f() { + try { + throw 1; + } catch (c) { + try { + throw 2; + } catch (o) { + var o = 3; + console.log(o); + } + } + console.log(o); + } + f(); + } + expect_exact: "var o=0;function f(){try{throw 1}catch(c){try{throw 2}catch(o){var o=3;console.log(o)}}console.log(o)}f();" + expect_stdout: [ + "3", + "undefined", + ] +} diff --git a/test/mocha/minify.js b/test/mocha/minify.js index db41cd8b85e..05e319440d6 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -354,7 +354,8 @@ describe("minify", function() { Uglify.minify(ast, { compress: { sequences: false - } + }, + mangle: false }); assert.ok(stat.body); assert.strictEqual(stat.print_to_string(), "a=x()"); diff --git a/test/run-tests.js b/test/run-tests.js index 0051c6c2f59..e95bbb83cd6 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -117,6 +117,10 @@ function run_compress_tests() { test.mangle.properties.reserved = quoted_props; U.reserve_quoted_keys(input, quoted_props); } + if (test.rename) { + input.figure_out_scope(test.mangle); + input.expand_names(test.mangle); + } var cmp = new U.Compressor(options, true); var output = cmp.compress(input); output.figure_out_scope(test.mangle);