diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2d991a5f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +Contributing +============ + +## Documentation + +Every new feature and API change should be accompanied by a README additon. + +## Testing + +All features and bugs should have tests that verify the fix. You can run all +tests using `npm test`. + +The most common type of test are tests that verify input and output of the +Uglify transforms. These tests exist in `test/compress`. New tests can be added +either to an existing file or in a new file `issue-xxx.js`. + +Tests that cannot be expressed as a simple AST can be found in `test/mocha`. + +## Code style + +- File encoding must be `UTF-8`. +- `LF` is always used as a line ending. +- Statements end with semicolons. +- Indentation uses 4 spaces, switch `case` 2 spaces. +- Identifiers use `snake_case`. +- Strings use double quotes (`"`). +- Use a trailing comma for multiline array and object literals to minimize diffs. +- The Uglify code only uses ES5, even in the `harmony` branch. +- Line length should be at most 80 cols, except when it is easier to read a + longer line. +- If both sides of a comparison are of the same type, `==` and `!=` are used. +- Multiline conditions place `&&` and `||` first on the line. + +**Example feature** + +```js +OPT(AST_Debugger, function(self, compressor) { + if (compressor.option("drop_debugger")) + return make_node(AST_EmptyStatement, self); + return self; +}); +``` + +**Example test case** + +```js +drop_debugger: { + options = { + drop_debugger: true, + } + input: { + debugger; + if (foo) debugger; + } + expect: { + if (foo); + } +} +``` + + diff --git a/LICENSE b/LICENSE index dd7706f0..4fdaa855 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ UglifyJS is released under the BSD license: -Copyright 2012-2013 (c) Mihai Bazon +Copyright 2012-2018 (c) Mihai Bazon Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/README.md b/README.md index 047f2a28..842cfd3a 100644 --- a/README.md +++ b/README.md @@ -1173,3 +1173,27 @@ To enable fast minify mode with the API use: ```js UglifyJS.minify(code, { compress: false, mangle: true }); ``` + +#### Source maps and debugging + +Various `compress` transforms that simplify, rearrange, inline and remove code +are known to have an adverse effect on debugging with source maps. This is +expected as code is optimized and mappings are often simply not possible as +some code no longer exists. For highest fidelity in source map debugging +disable the Uglify `compress` option and just use `mangle`. + +### Compiler assumptions + +To allow for better optimizations, the compiler makes various assumptions: + +- `.toString()` and `.valueOf()` don't have side effects, and for built-in + objects they have not been overridden. +- `undefined`, `NaN` and `Infinity` have not been externally redefined. +- `arguments.callee`, `arguments.caller` and `Function.prototype.caller` are not used. +- The code doesn't expect the contents of `Function.prototype.toString()` or + `Error.prototype.stack` to be anything in particular. +- Getting and setting properties on a plain object does not cause other side effects + (using `.watch()` or `Proxy`). +- Object properties can be added, removed and modified (not prevented with + `Object.defineProperty()`, `Object.defineProperties()`, `Object.freeze()`, + `Object.preventExtensions()` or `Object.seal()`). diff --git a/lib/compress.js b/lib/compress.js index 5bfa9099..3744e552 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -327,6 +327,7 @@ merge(Compressor.prototype, { function reset_def(compressor, def) { def.assignments = 0; + def.chained = false; def.direct_access = false; def.escaped = false; if (def.scope.uses_eval || def.scope.uses_with) { @@ -512,19 +513,27 @@ merge(Compressor.prototype, { node.left.walk(suppressor); return; } - if (node.operator != "=" || !(node.left instanceof AST_SymbolRef)) return; + if (!(node.left instanceof AST_SymbolRef)) return; var d = node.left.definition(); - if (safe_to_assign(tw, d, node.right)) { - d.references.push(node.left); - d.assignments++; - d.fixed = function() { - return node.right; - }; - mark(tw, d, false); - node.right.walk(tw); - mark(tw, d, true); - return true; - } + var fixed = d.fixed; + if (!fixed && node.operator != "=") return; + if (!safe_to_assign(tw, d, node.right)) return; + d.references.push(node.left); + d.assignments++; + if (node.operator != "=") d.chained = true; + d.fixed = node.operator == "=" ? function() { + return node.right; + } : function() { + return make_node(AST_Binary, node, { + operator: node.operator.slice(0, -1), + left: fixed instanceof AST_Node ? fixed : fixed(), + right: node.right + }); + }; + mark(tw, d, false); + node.right.walk(tw); + mark(tw, d, true); + return true; }); def(AST_Binary, function(tw) { if (!lazy_op(this.operator)) return; @@ -722,6 +731,32 @@ merge(Compressor.prototype, { if (this.bfinally) this.bfinally.walk(tw); return true; }); + def(AST_Unary, function(tw, descend) { + var node = this; + if (node.operator != "++" && node.operator != "--") return; + if (!(node.expression instanceof AST_SymbolRef)) return; + var d = node.expression.definition(); + var fixed = d.fixed; + if (!fixed) return; + if (!safe_to_assign(tw, d, true)) return; + d.references.push(node.expression); + d.assignments++; + d.chained = true; + d.fixed = function() { + return make_node(AST_Binary, node, { + operator: node.operator.slice(0, -1), + left: make_node(AST_UnaryPrefix, node, { + operator: "+", + expression: fixed instanceof AST_Node ? fixed : fixed() + }), + right: make_node(AST_Number, node, { + value: 1 + }) + }); + }; + mark(tw, d, true); + return true; + }); def(AST_VarDef, function(tw, descend) { var node = this; if (node.name instanceof AST_Destructuring) { @@ -729,19 +764,19 @@ merge(Compressor.prototype, { return; } var d = node.name.definition(); - if (safe_to_assign(tw, d, node.value)) { - if (node.value) { + if (node.value) { + if (safe_to_assign(tw, d, node.value)) { d.fixed = function() { return node.value; }; tw.loop_ids[d.id] = tw.in_loop; mark(tw, d, false); descend(); + mark(tw, d, true); + return true; + } else { + d.fixed = false; } - mark(tw, d, true); - return true; - } else if (node.value) { - d.fixed = false; } }); def(AST_While, function(tw, descend, compressor) { @@ -995,32 +1030,11 @@ merge(Compressor.prototype, { var stat_index = statements.length; var scanner = new TreeTransformer(function(node, descend) { if (abort) return node; - // Scan case expressions first in a switch statement - if (node instanceof AST_Switch) { - if (!hit) { - if (node !== hit_stack[hit_index]) return node; - hit_index++; - } - node.expression = node.expression.transform(scanner); - for (var i = 0, len = node.body.length; !abort && i < len; i++) { - var branch = node.body[i]; - if (branch instanceof AST_Case) { - if (!hit) { - if (branch !== hit_stack[hit_index]) continue; - hit_index++; - } - branch.expression = branch.expression.transform(scanner); - if (side_effects || !replace_all) break; - } - } - abort = true; - return node; - } // Skip nodes before `candidate` as quickly as possible if (!hit) { if (node !== hit_stack[hit_index]) return node; hit_index++; - if (hit_index < hit_stack.length) return; + if (hit_index < hit_stack.length) return handle_custom_scan_order(node); hit = true; stop_after = find_stop(node, 0); if (stop_after === node) abort = true; @@ -1034,6 +1048,7 @@ merge(Compressor.prototype, { || node instanceof AST_Debugger || node instanceof AST_Destructuring || node instanceof AST_IterationStatement && !(node instanceof AST_For) + || node instanceof AST_LoopControl || node instanceof AST_Try || node instanceof AST_With || parent instanceof AST_For && node !== parent.init @@ -1042,10 +1057,21 @@ merge(Compressor.prototype, { abort = true; return node; } + // Stop only if candidate is found within conditional branches + if (!stop_if_hit && (side_effects || !replace_all) + && (parent instanceof AST_Binary && lazy_op(parent.operator) && parent.left !== node + || parent instanceof AST_Conditional && parent.condition !== node + || parent instanceof AST_If && parent.condition !== node)) { + stop_if_hit = parent; + } // Replace variable with assignment when found if (can_replace && !(node instanceof AST_SymbolDeclaration) && lhs.equivalent_to(node)) { + if (stop_if_hit) { + abort = true; + return node; + } if (is_lhs(node, parent)) { if (value_def) replaced++; return node; @@ -1090,26 +1116,25 @@ merge(Compressor.prototype, { var sym; if (node instanceof AST_Call || node instanceof AST_Exit + && (side_effects || lhs instanceof AST_PropAccess || may_modify(lhs)) || node instanceof AST_PropAccess && (side_effects || node.expression.may_throw_on_access(compressor)) || node instanceof AST_SymbolRef - && (lvalues[node.name] - || side_effects && !references_in_scope(node.definition())) - || (sym = lhs_or_def(node)) + && (lvalues[node.name] || side_effects && may_modify(node)) + || node instanceof AST_VarDef && node.value + && (node.name.name in lvalues || side_effects && may_modify(node.name)) + || (sym = is_lhs(node.left, node)) && (sym instanceof AST_PropAccess || sym.name in lvalues) || may_throw - && (in_try ? node.has_side_effects(compressor) : side_effects_external(node)) - || (side_effects || !replace_all) - && (parent instanceof AST_Binary && lazy_op(parent.operator) - || parent instanceof AST_Conditional - || parent instanceof AST_If)) { + && (in_try ? node.has_side_effects(compressor) : side_effects_external(node))) { stop_after = node; if (node instanceof AST_Scope) abort = true; } - // Skip (non-executed) functions - if (node instanceof AST_Scope) return node; + return handle_custom_scan_order(node); }, function(node) { - if (!abort && stop_after === node) abort = true; + if (abort) return; + if (stop_after === node) abort = true; + if (stop_if_hit === node) stop_if_hit = null; }); var multi_replacer = new TreeTransformer(function(node) { if (abort) return node; @@ -1148,6 +1173,7 @@ merge(Compressor.prototype, { var candidate = hit_stack[hit_stack.length - 1]; var value_def = null; var stop_after = null; + var stop_if_hit = 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 @@ -1191,6 +1217,28 @@ merge(Compressor.prototype, { } } + function handle_custom_scan_order(node) { + // Skip (non-executed) functions + if (node instanceof AST_Scope) return node; + // Scan case expressions first in a switch statement + if (node instanceof AST_Switch) { + node.expression = node.expression.transform(scanner); + for (var i = 0, len = node.body.length; !abort && i < len; i++) { + var branch = node.body[i]; + if (branch instanceof AST_Case) { + if (!hit) { + if (branch !== hit_stack[hit_index]) continue; + hit_index++; + } + branch.expression = branch.expression.transform(scanner); + if (side_effects || !replace_all) break; + } + } + abort = true; + return node; + } + } + function has_overlapping_symbol(fn, arg, fn_strict) { var found = false, scan_this = !(fn instanceof AST_Arrow); arg.walk(new TreeWalker(function(node, descend) { @@ -1334,18 +1382,49 @@ merge(Compressor.prototype, { hit_stack.pop(); } - function find_stop(node, level) { + function find_stop(node, level, write_only) { var parent = scanner.parent(level); - if (parent instanceof AST_Binary) return node; + if (parent instanceof AST_Assign) { + if (write_only + && !(parent.left instanceof AST_PropAccess + || parent.left.name in lvalues)) { + return find_stop(parent, level + 1, write_only); + } + return node; + } + if (parent instanceof AST_Binary) { + if (write_only && (!lazy_op(parent.operator) || parent.left === node)) { + return find_stop(parent, level + 1, write_only); + } + return node; + } 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_Conditional) { + if (write_only && parent.condition === node) { + return find_stop(parent, level + 1, write_only); + } + return node; + } + if (parent instanceof AST_Definitions) { + return find_stop(parent, level + 1, true); + } + if (parent instanceof AST_Exit) { + return write_only ? find_stop(parent, level + 1, write_only) : node; + } + if (parent instanceof AST_If) { + if (write_only && parent.condition === node) { + return find_stop(parent, level + 1, write_only); + } + 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_Sequence) { + return find_stop(parent, level + 1, parent.tail_node() !== node); + } + if (parent instanceof AST_SimpleStatement) { + return find_stop(parent, level + 1, true); + } if (parent instanceof AST_Switch) return node; if (parent instanceof AST_VarDef) return node; return null; @@ -1394,11 +1473,6 @@ merge(Compressor.prototype, { return lvalues; } - function lhs_or_def(node) { - if (node instanceof AST_VarDef) return node.value && node.name; - return is_lhs(node.left, node); - } - function remove_candidate(expr) { if (expr.name instanceof AST_SymbolFunarg) { var iife = compressor.parent(), argnames = compressor.self().argnames; @@ -1437,31 +1511,25 @@ merge(Compressor.prototype, { return get_rvalue(expr).has_side_effects(compressor); } - function references_in_scope(def) { - if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return true; - if (def.scope.get_defun_scope() !== scope) return false; - return def.references.every(function(ref) { + function may_modify(sym) { + var def = sym.definition(); + if (def.orig.length == 1 && def.orig[0] instanceof AST_SymbolDefun) return false; + if (def.scope.get_defun_scope() !== scope) return true; + return !all(def.references, function(ref) { return ref.scope.get_defun_scope() === scope; }); } function side_effects_external(node, lhs) { - if (node instanceof AST_Assign) { - return side_effects_external(node.left, true) - || side_effects_external(node.right); - } - if (node instanceof AST_Definitions) return false; + if (node instanceof AST_Assign) return side_effects_external(node.left, true); if (node instanceof AST_Unary) return side_effects_external(node.expression, true); if (node instanceof AST_VarDef) return node.value && side_effects_external(node.value); if (lhs) { if (node instanceof AST_Dot) return side_effects_external(node.expression, true); - if (node instanceof AST_Sub) { - return side_effects_external(node.expression, true) - || side_effects_external(node.property); - } + if (node instanceof AST_Sub) return side_effects_external(node.expression, true); if (node instanceof AST_SymbolRef) return node.definition().scope !== scope; } - return node.has_side_effects(compressor); + return false; } } @@ -2291,6 +2359,8 @@ merge(Compressor.prototype, { "split", "substr", "substring", + "toLowerCase", + "toUpperCase", "trim", ].concat(object_fns), }; @@ -2843,8 +2913,13 @@ merge(Compressor.prototype, { return any(this.elements, compressor); }); def(AST_Assign, function(compressor){ - return this.operator != "=" && this.left.may_throw(compressor) - || this.right.may_throw(compressor); + if (this.right.may_throw(compressor)) return true; + if (!compressor.has_directive("use strict") + && this.operator == "=" + && this.left instanceof AST_SymbolRef) { + return false; + } + return this.left.may_throw(compressor); }); def(AST_Binary, function(compressor){ return this.left.may_throw(compressor) @@ -3126,7 +3201,7 @@ merge(Compressor.prototype, { } else { var node_def = def.name.definition();; initializations.add(node_def.id, def.value); - if (def.name.fixed_value() === def.value) { + if (!node_def.chained && def.name.fixed_value() === def.value) { fixed_ids[node_def.id] = def; } } @@ -3374,7 +3449,7 @@ merge(Compressor.prototype, { && self.variables.get(sym.name) === (node_def = sym.definition())) { if (node instanceof AST_Assign) { node.right.walk(tw); - if (node.left.fixed_value() === node.right) { + if (!node_def.chained && node.left.fixed_value() === node.right) { fixed_ids[node_def.id] = node; } } @@ -5034,6 +5109,7 @@ merge(Compressor.prototype, { if (compressor.option("comparisons")) switch (self.operator) { case "===": case "!==": + var is_strict_comparison = true; if ((self.left.is_string(compressor) && self.right.is_string(compressor)) || (self.left.is_number(compressor) && self.right.is_number(compressor)) || (self.left.is_boolean() && self.right.is_boolean()) || @@ -5043,8 +5119,12 @@ merge(Compressor.prototype, { // XXX: intentionally falling down to the next case case "==": case "!=": + // void 0 == x => null == x + if (!is_strict_comparison && is_undefined(self.left, compressor)) { + self.left = make_node(AST_Null, self.left); + } // "undefined" == typeof x => undefined === x - if (compressor.option("typeofs") + else if (compressor.option("typeofs") && self.left instanceof AST_String && self.left.value == "undefined" && self.right instanceof AST_UnaryPrefix @@ -5065,6 +5145,35 @@ merge(Compressor.prototype, { return make_node(self.operator[0] == "=" ? AST_True : AST_False, self); } break; + case "&&": + case "||": + var lhs = self.left; + if (lhs.operator == self.operator) { + lhs = lhs.right; + } + if (lhs instanceof AST_Binary + && lhs.operator == (self.operator == "&&" ? "!==" : "===") + && self.right instanceof AST_Binary + && lhs.operator == self.right.operator + && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null + || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor)) + && !lhs.right.has_side_effects(compressor) + && lhs.right.equivalent_to(self.right.right)) { + var combined = make_node(AST_Binary, self, { + operator: lhs.operator.slice(0, -1), + left: make_node(AST_Null, self), + right: lhs.right + }); + if (lhs !== self.left) { + combined = make_node(AST_Binary, self, { + operator: self.operator, + left: self.left.left, + right: combined + }); + } + return combined; + } + break; } if (self.operator == "+" && compressor.in_boolean_context()) { var ll = self.left.evaluate(compressor); @@ -5521,7 +5630,7 @@ merge(Compressor.prototype, { var name_length = d.name.length; var overhead = 0; if (compressor.option("unused") && !compressor.exposed(d)) { - overhead = (name_length + 2 + value_length) / d.references.length; + overhead = (name_length + 2 + value_length) / (d.references.length - d.assignments); } d.should_replace = value_length <= name_length + overhead ? fn : false; } else { @@ -5642,6 +5751,7 @@ merge(Compressor.prototype, { if (in_try(level, parent instanceof AST_Throw)) break; if (is_reachable(def.scope, [ def ])) break; if (self.operator == "=") return self.right; + def.fixed = false; return make_node(AST_Binary, self, { operator: self.operator.slice(0, -1), left: self.left, diff --git a/package.json b/package.json index a36cae3b..eb8e902d 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.9", + "version": "3.3.10", "engines": { "node": ">=0.8.0" }, @@ -24,13 +24,13 @@ "LICENSE" ], "dependencies": { - "commander": "~2.13.0", + "commander": "~2.14.1", "source-map": "~0.6.1" }, "devDependencies": { - "acorn": "~5.3.0", + "acorn": "~5.4.1", "mocha": "~3.5.1", - "semver": "~5.4.1" + "semver": "~5.5.0" }, "scripts": { "test": "node test/run-tests.js" diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index c8acc0c0..09d06d21 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -52,13 +52,8 @@ collapse_vars_side_effects_1: { console.log.bind(console)(s.charAt(i++), s.charAt(i++), s.charAt(i++), 7); } function f2() { - var log = console.log.bind(console), - s = "abcdef", - i = 2, - x = s.charAt(i++), - y = s.charAt(i++), - z = s.charAt(i++); - log(x, i, y, z, 7); + var s = "abcdef", i = 2; + console.log.bind(console)(s.charAt(i++), 5, s.charAt(i++), s.charAt(i++), 7); } function f3() { var s = "abcdef", @@ -72,7 +67,7 @@ collapse_vars_side_effects_1: { var i = 10, x = i += 2, y = i += 3; - console.log.bind(console)(x, i += 4, y, i); + console.log.bind(console)(x, i += 4, y, 19); } f1(), f2(), f3(), f4(); } @@ -4414,3 +4409,413 @@ unsafe_builtin: { } expect_stdout: "1 4" } + +return_1: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var log = console.log; + function f(b, c) { + var a = c; + if (b) return b; + log(a); + } + f(false, 1); + f(true, 2); + } + expect: { + var log = console.log; + function f(b, c) { + if (b) return b; + log(c); + } + f(false, 1); + f(true, 2); + } + expect_stdout: "1" +} + +return_2: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var log = console.log; + function f(b, c) { + var a = c(); + if (b) return b; + log(a); + } + f(false, function() { return 1 }); + f(true, function() { return 2 }); + } + expect: { + var log = console.log; + function f(b, c) { + var a = c(); + if (b) return b; + log(a); + } + f(false, function() { return 1 }); + f(true, function() { return 2 }); + } + expect_stdout: "1" +} + +return_3: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var log = console.log; + function f(b, c) { + var a = b <<= c; + if (b) return b; + log(a); + } + f(false, 1); + f(true, 2); + } + expect: { + var log = console.log; + function f(b, c) { + var a = b <<= c; + if (b) return b; + log(a); + } + f(false, 1); + f(true, 2); + } + expect_stdout: "0" +} + +return_4: { + options = { + collapse_vars: true, + } + input: { + var a = "FAIL"; + (function(b) { + a = "PASS"; + return; + b(a); + })(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function(b) { + a = "PASS"; + return; + b(a); + })(); + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2858: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var b; + (function() { + function f() { + a++; + } + f(); + var c = f(); + var a = void 0; + c || (b = a); + })(); + console.log(b); + } + expect: { + var b; + (function() { + function f() { + a++; + } + f(); + var c = f(); + var a = void 0; + c || (b = a); + })(); + console.log(b); + } + expect_stdout: "undefined" +} + +cond_branch_1: { + options = { + collapse_vars: true, + sequences: true, + unused: true, + } + input: { + function f1(b, c) { + var log = console.log; + var a = ++c; + if (b) b++; + log(a, b); + } + function f2(b, c) { + var log = console.log; + var a = ++c; + b && b++; + log(a, b); + } + function f3(b, c) { + var log = console.log; + var a = ++c; + b ? b++ : b--; + log(a, b); + } + f1(1, 2); + f2(3, 4); + f3(5, 6); + } + expect: { + function f1(b, c) { + if (b) b++; + (0, console.log)(++c, b); + } + function f2(b, c) { + b && b++, + (0, console.log)(++c, b); + } + function f3(b, c) { + b ? b++ : b--, + (0, console.log)(++c, b); + } + f1(1, 2), + f2(3, 4), + f3(5, 6); + } + expect_stdout: [ + "3 2", + "5 4", + "7 6", + ] +} + +cond_branch_2: { + options = { + collapse_vars: true, + sequences: true, + unused: true, + } + input: { + function f1(b, c) { + var log = console.log; + var a = ++c; + if (b) b += a; + log(a, b); + } + function f2(b, c) { + var log = console.log; + var a = ++c; + b && (b += a); + log(a, b); + } + function f3(b, c) { + var log = console.log; + var a = ++c; + b ? b += a : b--; + log(a, b); + } + f1(1, 2); + f2(3, 4); + f3(5, 6); + } + expect: { + function f1(b, c) { + var a = ++c; + if (b) b += a; + (0, console.log)(a, b); + } + function f2(b, c) { + var a = ++c; + b && (b += a), + (0, console.log)(a, b); + } + function f3(b, c) { + var a = ++c; + b ? b += a : b--, + (0, console.log)(a, b); + } + f1(1, 2), + f2(3, 4), + f3(5, 6); + } + expect_stdout: [ + "3 4", + "5 8", + "7 12", + ] +} + +cond_branch_switch: { + options = { + collapse_vars: true, + } + input: { + var c = 0; + if (c = 1 + c, 0) switch (c = 1 + c) { + } + console.log(c); + } + expect: { + var c = 0; + if (c = 1 + c, 0) switch (c = 1 + c) { + } + console.log(c); + } + expect_stdout: "1" +} + +issue_2873_1: { + options = { + collapse_vars: true, + } + input: { + var b = 1, c = 0; + do { + c++; + if (!--b) break; + c = 1 + c; + } while (0); + console.log(b, c); + } + expect: { + var b = 1, c = 0; + do { + c++; + if (!--b) break; + c = 1 + c; + } while (0); + console.log(b, c); + } + expect_stdout: "0 1" +} + +issue_2873_2: { + options = { + collapse_vars: true, + } + input: { + var b = 1, c = 0; + do { + c++; + if (!--b) continue; + c = 1 + c; + } while (0); + console.log(b, c); + } + expect: { + var b = 1, c = 0; + do { + c++; + if (!--b) continue; + c = 1 + c; + } while (0); + console.log(b, c); + } + expect_stdout: "0 1" +} + +issue_2878: { + options = { + collapse_vars: true, + sequences: true, + } + input: { + var c = 0; + (function (a, b) { + function f2() { + if (a) c++; + } + b = f2(); + a = 1; + b && b.b; + f2(); + })(); + console.log(c); + } + expect: { + var c = 0; + (function (a, b) { + function f2() { + if (a) c++; + } + b = f2(), + a = 1, + b && b.b, + f2(); + })(), + console.log(c); + } + expect_stdout: "1" +} + +issue_2891_1: { + options = { + collapse_vars: true, + } + input: { + var a = "PASS", b; + try { + b = c.p = 0; + a = "FAIL"; + b(); + } catch (e) { + } + console.log(a); + } + expect: { + var a = "PASS", b; + try { + b = c.p = 0; + a = "FAIL"; + b(); + } catch (e) { + } + console.log(a); + } + expect_stdout: "PASS" +} + +issue_2891_2: { + options = { + collapse_vars: true, + } + input: { + "use strict"; + var a = "PASS", b; + try { + b = c = 0; + a = "FAIL"; + b(); + } catch (e) { + } + console.log(a); + } + expect: { + "use strict"; + var a = "PASS", b; + try { + b = c = 0; + a = "FAIL"; + b(); + } catch (e) { + } + console.log(a); + } + expect_stdout: true +} diff --git a/test/compress/comparing.js b/test/compress/comparing.js index e374b585..d56445e0 100644 --- a/test/compress/comparing.js +++ b/test/compress/comparing.js @@ -112,3 +112,206 @@ self_comparison_2: { } expect_stdout: "false true" } + +issue_2857_1: { + options = { + comparisons: true, + } + input: { + function f1(a) { + a === undefined || a === null; + a === undefined || a !== null; + a !== undefined || a === null; + a !== undefined || a !== null; + a === undefined && a === null; + a === undefined && a !== null; + a !== undefined && a === null; + a !== undefined && a !== null; + } + function f2(a) { + a === null || a === undefined; + a === null || a !== undefined; + a !== null || a === undefined; + a !== null || a !== undefined; + a === null && a === undefined; + a === null && a !== undefined; + a !== null && a === undefined; + a !== null && a !== undefined; + } + } + expect: { + function f1(a) { + null == a; + void 0 === a || null !== a; + void 0 !== a || null === a; + void 0 !== a || null !== a; + void 0 === a && null === a; + void 0 === a && null !== a; + void 0 !== a && null === a; + null != a; + } + function f2(a) { + null == a; + null === a || void 0 !== a; + null !== a || void 0 === a; + null !== a || void 0 !== a; + null === a && void 0 === a; + null === a && void 0 !== a; + null !== a && void 0 === a; + null != a; + } + } +} + +issue_2857_2: { + options = { + comparisons: true, + } + input: { + function f(a, p) { + a === undefined || a === null || p; + a === undefined || a !== null || p; + a !== undefined || a === null || p; + a !== undefined || a !== null || p; + a === undefined && a === null || p; + a === undefined && a !== null || p; + a !== undefined && a === null || p; + a !== undefined && a !== null || p; + } + } + expect: { + function f(a, p) { + null == a || p; + void 0 === a || null !== a || p; + void 0 !== a || null === a || p; + void 0 !== a || null !== a || p; + void 0 === a && null === a || p; + void 0 === a && null !== a || p; + void 0 !== a && null === a || p; + null != a || p; + } + } +} + +issue_2857_3: { + options = { + comparisons: true, + } + input: { + function f(a, p) { + a === undefined || a === null && p; + a === undefined || a !== null && p; + a !== undefined || a === null && p; + a !== undefined || a !== null && p; + a === undefined && a === null && p; + a === undefined && a !== null && p; + a !== undefined && a === null && p; + a !== undefined && a !== null && p; + } + } + expect: { + function f(a, p) { + void 0 === a || null === a && p; + void 0 === a || null !== a && p; + void 0 !== a || null === a && p; + void 0 !== a || null !== a && p; + void 0 === a && null === a && p; + void 0 === a && null !== a && p; + void 0 !== a && null === a && p; + null != a && p; + } + } +} + +issue_2857_4: { + options = { + comparisons: true, + } + input: { + function f(a, p) { + p || a === undefined || a === null; + p || a === undefined || a !== null; + p || a !== undefined || a === null; + p || a !== undefined || a !== null; + p || a === undefined && a === null; + p || a === undefined && a !== null; + p || a !== undefined && a === null; + p || a !== undefined && a !== null; + } + } + expect: { + function f(a, p) { + p || null == a; + p || void 0 === a || null !== a; + p || void 0 !== a || null === a; + p || void 0 !== a || null !== a; + p || void 0 === a && null === a; + p || void 0 === a && null !== a; + p || void 0 !== a && null === a; + p || null != a; + } + } +} + +issue_2857_5: { + options = { + comparisons: true, + } + input: { + function f(a, p) { + p && a === undefined || a === null; + p && a === undefined || a !== null; + p && a !== undefined || a === null; + p && a !== undefined || a !== null; + p && a === undefined && a === null; + p && a === undefined && a !== null; + p && a !== undefined && a === null; + p && a !== undefined && a !== null; + } + } + expect: { + function f(a, p) { + p && void 0 === a || null === a; + p && void 0 === a || null !== a; + p && void 0 !== a || null === a; + p && void 0 !== a || null !== a; + p && void 0 === a && null === a; + p && void 0 === a && null !== a; + p && void 0 !== a && null === a; + p && null != a; + } + } +} + +issue_2857_6: { + options = { + comparisons: true, + pure_getters: "strict", + reduce_vars: true, + } + input: { + function f(a) { + if (({}).b === undefined || {}.b === null) + return a.b !== undefined && a.b !== null; + } + console.log(f({ + a: [ null ], + get b() { + return this.a.shift(); + } + })); + } + expect: { + function f(a) { + if (null == {}.b) + return void 0 !== a.b && null !== a.b; + } + console.log(f({ + a: [ null ], + get b() { + return this.a.shift(); + } + })); + } + expect_stdout: "true" +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index e3c0d549..3ac104ee 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -1178,3 +1178,41 @@ unsafe_builtin: { z; } } + +issue_2860_1: { + options = { + dead_code: true, + evaluate: true, + reduce_vars: true, + } + input: { + console.log(function(a) { + return a ^= 1; + }()); + } + expect: { + console.log(function(a) { + return 1 ^ a; + }()); + } + expect_stdout: "1" +} + +issue_2860_2: { + options = { + dead_code: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + } + input: { + console.log(function(a) { + return a ^= 1; + }()); + } + expect: { + console.log(1); + } + expect_stdout: "1" +} diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 7792dc94..e1ab2b30 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1467,3 +1467,58 @@ issue_2822: { } expect_stdout: "PASS" } + +string_case: { + options = { + evaluate: true, + unsafe: true, + } + input: { + console.log("İ".toLowerCase().charCodeAt(0)); + console.log("I".toLowerCase().charCodeAt(0)); + console.log("Ş".toLowerCase().charCodeAt(0)); + console.log("Ğ".toLowerCase().charCodeAt(0)); + console.log("Ü".toLowerCase().charCodeAt(0)); + console.log("Ö".toLowerCase().charCodeAt(0)); + console.log("Ç".toLowerCase().charCodeAt(0)); + console.log("i".toUpperCase().charCodeAt(0)); + console.log("ı".toUpperCase().charCodeAt(0)); + console.log("ş".toUpperCase().charCodeAt(0)); + console.log("ğ".toUpperCase().charCodeAt(0)); + console.log("ü".toUpperCase().charCodeAt(0)); + console.log("ö".toUpperCase().charCodeAt(0)); + console.log("ç".toUpperCase().charCodeAt(0)); + } + expect: { + console.log(105); + console.log(105); + console.log(351); + console.log(287); + console.log(252); + console.log(246); + console.log(231); + console.log(73); + console.log(73); + console.log(350); + console.log(286); + console.log(220); + console.log(214); + console.log(199); + } + expect_stdout: [ + "105", + "105", + "351", + "287", + "252", + "246", + "231", + "73", + "73", + "350", + "286", + "220", + "214", + "199", + ] +} diff --git a/test/compress/issue-2871.js b/test/compress/issue-2871.js new file mode 100644 index 00000000..43c8352b --- /dev/null +++ b/test/compress/issue-2871.js @@ -0,0 +1,37 @@ +comparison_with_undefined: { + options = { + comparisons: true, + } + input: { + a == undefined; + a != undefined; + a === undefined; + a !== undefined; + + undefined == a; + undefined != a; + undefined === a; + undefined !== a; + + void 0 == a; + void 0 != a; + void 0 === a; + void 0 !== a; + } + expect: { + null == a; + null != a; + void 0 === a; + void 0 !== a; + + null == a; + null != a; + void 0 === a; + void 0 !== a; + + null == a; + null != a; + void 0 === a; + void 0 !== a; + } +} diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 311ad7c4..2faae1d2 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -76,14 +76,12 @@ modified: { console.log(a + 1); console.log(b + 1); } - function f1() { var a = 1, b = 2; --b; console.log(a + 1); console.log(b + 1); } - function f2() { var a = 1, b = 2, c = 3; b = c; @@ -92,7 +90,6 @@ modified: { console.log(a + c); console.log(a + b + c); } - function f3() { var a = 1, b = 2, c = 3; b *= c; @@ -101,7 +98,6 @@ modified: { console.log(a + c); console.log(a + b + c); } - function f4() { var a = 1, b = 2, c = 3; if (a) { @@ -114,28 +110,26 @@ modified: { console.log(a + c); console.log(a + b + c); } - function f5(a) { B = a; - console.log(A ? 'yes' : 'no'); - console.log(B ? 'yes' : 'no'); + console.log(typeof A ? "yes" : "no"); + console.log(typeof B ? "yes" : "no"); } + f0(), f1(), f2(), f3(), f4(), f5(); } expect: { function f0() { var b = 2; b++; console.log(2); - console.log(b + 1); + console.log(4); } - function f1() { var b = 2; --b; console.log(2); - console.log(b + 1); + console.log(2); } - function f2() { 3; console.log(4); @@ -143,16 +137,14 @@ modified: { console.log(4); console.log(7); } - function f3() { var b = 2; b *= 3; - console.log(1 + b); - console.log(b + 3); + console.log(7); + console.log(9); console.log(4); - console.log(1 + b + 3); + console.log(10); } - function f4() { var b = 2, c = 3; b = c; @@ -161,13 +153,33 @@ modified: { console.log(1 + c); console.log(1 + b + c); } - function f5(a) { B = a; - console.log(A ? 'yes' : 'no'); - console.log(B ? 'yes' : 'no'); + console.log(typeof A ? "yes" : "no"); + console.log(typeof B ? "yes" : "no"); } + f0(), f1(), f2(), f3(), f4(), f5(); } + expect_stdout: [ + "2", + "4", + "2", + "2", + "4", + "6", + "4", + "7", + "7", + "9", + "4", + "10", + "4", + "6", + "4", + "7", + "yes", + "yes", + ] } unsafe_evaluate: { @@ -745,7 +757,7 @@ iife: { expect: { !function(a, b, c) { b++; - console.log(0, 1 * b, 5); + console.log(0, 3, 5); }(1, 2, 3); } expect_stdout: true @@ -766,7 +778,7 @@ iife_new: { expect: { var A = new function(a, b, c) { b++; - console.log(0, 1 * b, 5); + console.log(0, 3, 5); }(1, 2, 3); } expect_stdout: true @@ -5973,3 +5985,141 @@ issue_2836: { } expect_stdout: "PASS" } + +lvalues_def_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var b = 1; + var a = b++, b = NaN; + console.log(a, b); + } + expect: { + var b = 1; + var a = b++; + b = NaN; + console.log(a, b); + } + expect_stdout: "1 NaN" +} + +lvalues_def_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var b = 1; + var a = b += 1, b = NaN; + console.log(a, b); + } + expect: { + var b = 1; + var a = b += 1; + b = NaN; + console.log(a, b); + } + expect_stdout: "2 NaN" +} + +chained_assignments: { + options = { + evaluate: true, + inline: true, + reduce_vars: true, + sequences: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + function f() { + var a = [0x5e, 0xad, 0xbe, 0xef]; + var b = 0; + b |= a[0]; + b <<= 8; + b |= a[1]; + b <<= 8; + b |= a[2]; + b <<= 8; + b |= a[3]; + return b; + } + console.log(f().toString(16)); + } + expect: { + console.log("5eadbeef"); + } + expect_stdout: "5eadbeef" +} + +issue_2860_1: { + options = { + dead_code: true, + evaluate: true, + reduce_vars: true, + } + input: { + console.log(function(a) { + return a ^= 1; + a ^= 2; + }()); + } + expect: { + console.log(function(a) { + return 1 ^ a; + }()); + } + expect_stdout: "1" +} + +issue_2860_2: { + options = { + dead_code: true, + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + } + input: { + console.log(function(a) { + return a ^= 1; + a ^= 2; + }()); + } + expect: { + console.log(1); + } + expect_stdout: "1" +} + +issue_2869: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + var c = "FAIL"; + (function f(a) { + var a; + if (!f) a = 0; + if (a) c = "PASS"; + })(1); + console.log(c); + } + expect: { + var c = "FAIL"; + (function f(a) { + var a; + if (!f) a = 0; + if (a) c = "PASS"; + })(1); + console.log(c); + } + expect_stdout: "PASS" +} diff --git a/test/run-tests.js b/test/run-tests.js index deb1f954..76b3013a 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -208,7 +208,7 @@ function run_compress_tests() { }); return false; } - if (test.reminify && !reminify(test.options, input_code, input_formatted, test.expect_stdout)) { + if (0 && test.reminify && !reminify(test.options, input_code, input_formatted, test.expect_stdout)) { return false; } } diff --git a/test/ufuzz.js b/test/ufuzz.js index bbf4fa09..f717f00b 100644 --- a/test/ufuzz.js +++ b/test/ufuzz.js @@ -1024,7 +1024,6 @@ 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);