Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac7b5c07d7 | ||
|
|
0cd4a199b0 | ||
|
|
35435d4bd3 | ||
|
|
d0bb147639 | ||
|
|
4723b4541e | ||
|
|
9d23ba0a22 | ||
|
|
a08d42555a | ||
|
|
fd7ad8e779 | ||
|
|
a36c5472d2 | ||
|
|
8bfd891c09 | ||
|
|
ef9f7ca3e7 | ||
|
|
acc443b2cf | ||
|
|
f87e7be12c | ||
|
|
c0614654d9 | ||
|
|
0358637725 | ||
|
|
63b5b6d2b3 | ||
|
|
e675262d51 | ||
|
|
c1e771a89a | ||
|
|
bc7a88baea | ||
|
|
018e0350f8 | ||
|
|
d37ee4d41c |
2
.github/workflows/ufuzz.yml
vendored
2
.github/workflows/ufuzz.yml
vendored
@@ -32,6 +32,8 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install GNU Core Utilities
|
||||
if: ${{ startsWith(matrix.os, 'macos') }}
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
shell: bash
|
||||
run: |
|
||||
brew install coreutils
|
||||
|
||||
@@ -1254,3 +1254,9 @@ To allow for better optimizations, the compiler makes various assumptions:
|
||||
}()) => b)());
|
||||
```
|
||||
UglifyJS may modify the input which in turn may suppress those errors.
|
||||
- Some arithmetic operations with `BigInt` may throw `TypeError`:
|
||||
```javascript
|
||||
1n + 1;
|
||||
// TypeError: can't convert BigInt to number
|
||||
```
|
||||
UglifyJS may modify the input which in turn may suppress those errors.
|
||||
|
||||
17
lib/ast.js
17
lib/ast.js
@@ -249,7 +249,7 @@ var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_s
|
||||
enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes",
|
||||
functions: "[Object/S] like `variables`, but only lists function declarations",
|
||||
parent_scope: "[AST_Scope?/S] link to the parent scope",
|
||||
variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope",
|
||||
variables: "[Object/S] a map of name ---> SymbolDef for all variables/functions defined in this scope",
|
||||
},
|
||||
clone: function(deep) {
|
||||
var node = this._clone(deep);
|
||||
@@ -472,7 +472,7 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", {
|
||||
var AST_Toplevel = DEFNODE("Toplevel", "globals", {
|
||||
$documentation: "The toplevel scope",
|
||||
$propdoc: {
|
||||
globals: "[Object/S] a map of name -> SymbolDef for all undeclared names",
|
||||
globals: "[Object/S] a map of name ---> SymbolDef for all undeclared names",
|
||||
},
|
||||
wrap: function(name) {
|
||||
var body = this.body;
|
||||
@@ -1440,6 +1440,19 @@ var AST_Number = DEFNODE("Number", "value", {
|
||||
},
|
||||
_validate: function() {
|
||||
if (typeof this.value != "number") throw new Error("value must be number");
|
||||
if (!isFinite(this.value)) throw new Error("value must be finite");
|
||||
if (this.value < 0) throw new Error("value cannot be negative");
|
||||
},
|
||||
}, AST_Constant);
|
||||
|
||||
var AST_BigInt = DEFNODE("BigInt", "value", {
|
||||
$documentation: "A BigInt literal",
|
||||
$propdoc: {
|
||||
value: "[string] the numeric representation",
|
||||
},
|
||||
_validate: function() {
|
||||
if (typeof this.value != "string") throw new Error("value must be string");
|
||||
if (this.value[0] == "-") throw new Error("value cannot be negative");
|
||||
},
|
||||
}, AST_Constant);
|
||||
|
||||
|
||||
397
lib/compress.js
397
lib/compress.js
@@ -382,6 +382,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
|
||||
var RE_POSITIVE_INTEGER = /^(0|[1-9][0-9]*)$/;
|
||||
(function(def) {
|
||||
def(AST_Node, noop);
|
||||
|
||||
@@ -603,7 +604,7 @@ merge(Compressor.prototype, {
|
||||
if (!is_arguments(def)) return;
|
||||
var key = node.property;
|
||||
if (key.is_constant()) key = key.value;
|
||||
if (!(key instanceof AST_Node) && !/^[1-9]*[0-9]$/.test(key)) return;
|
||||
if (!(key instanceof AST_Node) && !RE_POSITIVE_INTEGER.test(key)) return;
|
||||
def.reassigned = true;
|
||||
(key instanceof AST_Node ? def.scope.argnames : [ def.scope.argnames[key] ]).forEach(function(argname) {
|
||||
if (argname instanceof AST_SymbolFunarg) argname.definition().fixed = false;
|
||||
@@ -718,7 +719,7 @@ merge(Compressor.prototype, {
|
||||
var fn = this;
|
||||
fn.inlined = false;
|
||||
var iife = tw.parent();
|
||||
var hit = fn instanceof AST_AsyncFunction;
|
||||
var hit = is_async(fn);
|
||||
var aborts = false;
|
||||
fn.walk(new TreeWalker(function(node) {
|
||||
if (hit) return aborts = true;
|
||||
@@ -728,7 +729,7 @@ merge(Compressor.prototype, {
|
||||
if (aborts) push(tw);
|
||||
reset_variables(tw, compressor, fn);
|
||||
// Virtually turn IIFE parameters into variable definitions:
|
||||
// (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})()
|
||||
// (function(a,b) {...})(c,d) ---> (function() {var a=c,b=d; ...})()
|
||||
// So existing transformation rules can work on them.
|
||||
var safe = !fn.uses_arguments || tw.has_directive("use strict");
|
||||
fn.argnames.forEach(function(arg, i) {
|
||||
@@ -785,7 +786,10 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
var d = sym.definition();
|
||||
d.assignments++;
|
||||
if (fixed && !is_modified(compressor, tw, node, node.right, 0) && safe_to_assign(tw, d)) {
|
||||
if (fixed
|
||||
&& !is_modified(compressor, tw, node, node.right, 0)
|
||||
&& !sym.in_arg
|
||||
&& safe_to_assign(tw, d)) {
|
||||
push_ref(d, sym);
|
||||
mark(tw, d);
|
||||
if (d.single_use && left instanceof AST_Destructured) d.single_use = false;
|
||||
@@ -808,7 +812,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
var safe = safe_to_read(tw, d);
|
||||
node.right.walk(tw);
|
||||
if (safe && safe_to_assign(tw, d)) {
|
||||
if (safe && !left.in_arg && safe_to_assign(tw, d)) {
|
||||
push_ref(d, left);
|
||||
mark(tw, d);
|
||||
if (d.single_use) d.single_use = false;
|
||||
@@ -1116,7 +1120,7 @@ merge(Compressor.prototype, {
|
||||
var d = exp.definition();
|
||||
d.assignments++;
|
||||
var fixed = d.fixed;
|
||||
if (safe_to_read(tw, d) && safe_to_assign(tw, d)) {
|
||||
if (safe_to_read(tw, d) && !exp.in_arg && safe_to_assign(tw, d)) {
|
||||
push_ref(d, exp);
|
||||
mark(tw, d);
|
||||
if (d.single_use) d.single_use = false;
|
||||
@@ -2341,6 +2345,7 @@ merge(Compressor.prototype, {
|
||||
if (expr.operator == "="
|
||||
&& lhs instanceof AST_SymbolRef
|
||||
&& (def = lhs.definition()).references[0] === lhs
|
||||
&& !(scope.uses_arguments && is_funarg(def))
|
||||
&& !compressor.exposed(def)) {
|
||||
var referenced = def.references.length - def.replaced;
|
||||
if (referenced > 1) mangleable_var(expr.right);
|
||||
@@ -2353,6 +2358,7 @@ merge(Compressor.prototype, {
|
||||
var def = expr.name.definition();
|
||||
if (def.const_redefs) return;
|
||||
if (!member(expr.name, def.orig)) return;
|
||||
if (scope.uses_arguments && is_funarg(def)) return;
|
||||
var declared = def.orig.length - def.eliminated - (declare_only[def.name] || 0);
|
||||
var referenced = def.references.length - def.replaced - (assignments[def.name] || 0);
|
||||
if (declared > 1 && !(expr.name instanceof AST_SymbolFunarg)) {
|
||||
@@ -2407,7 +2413,8 @@ merge(Compressor.prototype, {
|
||||
if (expr instanceof AST_This) return rhs_exact_match;
|
||||
if (expr.is_truthy()) return rhs_fuzzy_match(true, return_false);
|
||||
if (expr.is_constant()) {
|
||||
return rhs_fuzzy_match(expr.evaluate(compressor), rhs_exact_match);
|
||||
var ev = expr.evaluate(compressor);
|
||||
if (!(ev instanceof AST_Node)) return rhs_fuzzy_match(ev, rhs_exact_match);
|
||||
}
|
||||
if (!(lhs instanceof AST_SymbolRef)) return false;
|
||||
if (!invariant(expr)) return false;
|
||||
@@ -2430,7 +2437,8 @@ merge(Compressor.prototype, {
|
||||
return true;
|
||||
}
|
||||
if (node.is_constant()) {
|
||||
return !node.evaluate(compressor) == !value;
|
||||
var ev = node.evaluate(compressor);
|
||||
if (!(ev instanceof AST_Node)) return !ev == !value;
|
||||
}
|
||||
}
|
||||
return fallback(node);
|
||||
@@ -2736,7 +2744,7 @@ merge(Compressor.prototype, {
|
||||
var in_bool = stat.body.in_bool || next instanceof AST_Return && next.in_bool;
|
||||
//---
|
||||
// pretty silly case, but:
|
||||
// if (foo()) return; return; => foo(); return;
|
||||
// if (foo()) return; return; ---> foo(); return;
|
||||
if (!value && !stat.alternative
|
||||
&& (in_lambda && !next || next instanceof AST_Return && !next.value)) {
|
||||
CHANGED = true;
|
||||
@@ -2746,7 +2754,7 @@ merge(Compressor.prototype, {
|
||||
continue;
|
||||
}
|
||||
//---
|
||||
// if (foo()) return x; return y; => return foo() ? x : y;
|
||||
// if (foo()) return x; return y; ---> return foo() ? x : y;
|
||||
if (!stat.alternative && next instanceof AST_Return) {
|
||||
CHANGED = true;
|
||||
stat = stat.clone();
|
||||
@@ -2756,7 +2764,7 @@ merge(Compressor.prototype, {
|
||||
continue;
|
||||
}
|
||||
//---
|
||||
// if (foo()) return x; [ return ; ] => return foo() ? x : undefined;
|
||||
// if (foo()) return x; [ return ; ] ---> return foo() ? x : undefined;
|
||||
if (!stat.alternative && !next && in_lambda && (in_bool || value && multiple_if_returns)) {
|
||||
CHANGED = true;
|
||||
stat = stat.clone();
|
||||
@@ -2767,7 +2775,7 @@ merge(Compressor.prototype, {
|
||||
continue;
|
||||
}
|
||||
//---
|
||||
// if (a) return b; if (c) return d; e; => return a ? b : c ? d : void e;
|
||||
// if (a) return b; if (c) return d; e; ---> return a ? b : c ? d : void e;
|
||||
//
|
||||
// if sequences is not enabled, this can lead to an endless loop (issue #866).
|
||||
// however, with sequences on this helps producing slightly better output for
|
||||
@@ -3811,6 +3819,14 @@ merge(Compressor.prototype, {
|
||||
return skip_directives(this.body);
|
||||
});
|
||||
|
||||
AST_Lambda.DEFMETHOD("length", function() {
|
||||
var argnames = this.argnames;
|
||||
for (var i = 0; i < argnames.length; i++) {
|
||||
if (argnames[i] instanceof AST_DefaultValue) break;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
|
||||
function try_evaluate(compressor, node) {
|
||||
var ev = node.evaluate(compressor);
|
||||
if (ev === node) return node;
|
||||
@@ -3964,6 +3980,7 @@ merge(Compressor.prototype, {
|
||||
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
||||
});
|
||||
def(AST_Accessor, return_this);
|
||||
def(AST_BigInt, return_this);
|
||||
def(AST_Node, return_this);
|
||||
def(AST_Constant, function() {
|
||||
return this.value;
|
||||
@@ -4151,6 +4168,9 @@ merge(Compressor.prototype, {
|
||||
case "<=" : result = left <= right; break;
|
||||
case ">" : result = left > right; break;
|
||||
case ">=" : result = left >= right; break;
|
||||
case "**":
|
||||
result = Math.pow(left, right);
|
||||
break;
|
||||
case "in":
|
||||
if (right && typeof right == "object" && HOP(right, left)) {
|
||||
result = true;
|
||||
@@ -4166,7 +4186,7 @@ merge(Compressor.prototype, {
|
||||
&& typeof result == "number"
|
||||
&& (this.operator == "+" || this.operator == "-")) {
|
||||
var digits = Math.max(0, decimals(left), decimals(right));
|
||||
// 53-bit significand => 15.95 decimal places
|
||||
// 53-bit significand ---> 15.95 decimal places
|
||||
if (digits < 16) return +result.toFixed(digits);
|
||||
}
|
||||
return result;
|
||||
@@ -4270,7 +4290,7 @@ merge(Compressor.prototype, {
|
||||
case "name":
|
||||
return val.node.name ? val.node.name.name : "";
|
||||
case "length":
|
||||
return val.node.argnames.length;
|
||||
return val.node.length();
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
@@ -4893,10 +4913,10 @@ merge(Compressor.prototype, {
|
||||
return self;
|
||||
});
|
||||
|
||||
function trim_block(node) {
|
||||
function trim_block(node, in_list) {
|
||||
switch (node.body.length) {
|
||||
case 0:
|
||||
return make_node(AST_EmptyStatement, node);
|
||||
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
|
||||
case 1:
|
||||
var stat = node.body[0];
|
||||
if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat;
|
||||
@@ -5698,19 +5718,33 @@ merge(Compressor.prototype, {
|
||||
unused_fn_names.push(node);
|
||||
}
|
||||
if (!(node instanceof AST_Accessor)) {
|
||||
if (node.rest) {
|
||||
node.rest = node.rest.transform(trimmer);
|
||||
if (!(node.uses_arguments && !tt.has_directive("use strict"))
|
||||
&& (node.rest instanceof AST_DestructuredArray && node.rest.elements.length == 0
|
||||
|| node.rest instanceof AST_DestructuredObject && node.rest.properties.length == 0)) {
|
||||
node.rest = null;
|
||||
}
|
||||
}
|
||||
var argnames = node.argnames;
|
||||
var trim = compressor.drop_fargs(node, parent) && !node.rest;
|
||||
for (var a = node.argnames, i = a.length; --i >= 0;) {
|
||||
var sym = a[i];
|
||||
var default_length = trim ? -1 : node.length();
|
||||
for (var i = argnames.length; --i >= 0;) {
|
||||
var sym = argnames[i];
|
||||
if (!(sym instanceof AST_SymbolFunarg)) {
|
||||
var arg = sym.transform(trimmer);
|
||||
if (arg) {
|
||||
trim = false;
|
||||
} else if (trim) {
|
||||
log(sym.name, "Dropping unused function argument {name}");
|
||||
a.pop();
|
||||
} else {
|
||||
log(sym.name, "Dropping unused default argument {name}");
|
||||
argnames.pop();
|
||||
} else if (i > default_length) {
|
||||
log(sym.name, "Dropping unused default argument assignment {name}");
|
||||
sym.name.__unused = true;
|
||||
a[i] = sym.name;
|
||||
argnames[i] = sym.name;
|
||||
} else {
|
||||
log(sym.name, "Dropping unused default argument value {name}");
|
||||
sym.value = make_node(AST_Number, sym, { value: 0 });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -5720,19 +5754,11 @@ merge(Compressor.prototype, {
|
||||
if (indexOf_assign(def, sym) < 0) sym.__unused = null;
|
||||
} else if (trim) {
|
||||
log(sym, "Dropping unused function argument {name}");
|
||||
a.pop();
|
||||
argnames.pop();
|
||||
} else {
|
||||
sym.__unused = true;
|
||||
}
|
||||
}
|
||||
if (node.rest) {
|
||||
node.rest = node.rest.transform(trimmer);
|
||||
if (!(node.uses_arguments && !tt.has_directive("use strict"))
|
||||
&& (node.rest instanceof AST_DestructuredArray && node.rest.elements.length == 0
|
||||
|| node.rest instanceof AST_DestructuredObject && node.rest.properties.length == 0)) {
|
||||
node.rest = null;
|
||||
}
|
||||
}
|
||||
fns_with_marked_args.push(node);
|
||||
}
|
||||
}
|
||||
@@ -5957,12 +5983,8 @@ merge(Compressor.prototype, {
|
||||
return node;
|
||||
}
|
||||
}, function(node, in_list) {
|
||||
if (node instanceof AST_BlockStatement) switch (node.body.length) {
|
||||
case 0:
|
||||
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
|
||||
case 1:
|
||||
var stat = node.body[0];
|
||||
if (!(stat instanceof AST_Const || stat instanceof AST_Let)) return stat;
|
||||
if (node instanceof AST_BlockStatement) {
|
||||
return trim_block(node, in_list);
|
||||
} else if (node instanceof AST_For) {
|
||||
// Certain combination of unused name + side effect leads to invalid AST:
|
||||
// https://github.com/mishoo/UglifyJS/issues/44
|
||||
@@ -7489,7 +7511,7 @@ merge(Compressor.prototype, {
|
||||
var exprs = [];
|
||||
for (var i = 0; i < stat.body.length; i++) {
|
||||
var line = stat.body[i];
|
||||
if (line instanceof AST_Defun) {
|
||||
if (is_defun(line)) {
|
||||
defuns.push(line);
|
||||
} else if (line instanceof AST_EmptyStatement) {
|
||||
continue;
|
||||
@@ -7506,7 +7528,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
return exprs;
|
||||
}
|
||||
if (stat instanceof AST_Defun) {
|
||||
if (is_defun(stat)) {
|
||||
defuns.push(stat);
|
||||
return [];
|
||||
}
|
||||
@@ -7844,11 +7866,11 @@ merge(Compressor.prototype, {
|
||||
if (fn.rest) {
|
||||
if (!(is_iife && compressor.option("rests"))) return;
|
||||
var insert = fn.argnames.length;
|
||||
for (var i = args.length; i < insert; i++) {
|
||||
args[i] = make_node(AST_Undefined, call).optimize(compressor);
|
||||
}
|
||||
args[insert] = make_node(AST_Array, call, { elements: args.splice(insert) });
|
||||
fn.argnames.push(fn.rest);
|
||||
args = args.slice(0, insert);
|
||||
while (args.length < insert) args.push(make_node(AST_Undefined, call).optimize(compressor));
|
||||
args.push(make_node(AST_Array, call, { elements: call.args.slice(insert) }));
|
||||
call.args = args;
|
||||
fn.argnames = fn.argnames.concat(fn.rest);
|
||||
fn.rest = null;
|
||||
}
|
||||
var pos = 0, last = 0;
|
||||
@@ -7938,6 +7960,7 @@ merge(Compressor.prototype, {
|
||||
if (compressor.option("unsafe")) {
|
||||
if (is_undeclared_ref(exp)) switch (exp.name) {
|
||||
case "Array":
|
||||
// Array(n) ---> [ , , ... , ]
|
||||
if (self.args.length == 1) {
|
||||
var first = self.args[0];
|
||||
if (first instanceof AST_Number) try {
|
||||
@@ -7945,9 +7968,7 @@ merge(Compressor.prototype, {
|
||||
if (length > 6) break;
|
||||
var elements = Array(length);
|
||||
for (var i = 0; i < length; i++) elements[i] = make_node(AST_Hole, self);
|
||||
return make_node(AST_Array, self, {
|
||||
elements: elements
|
||||
});
|
||||
return make_node(AST_Array, self, { elements: elements });
|
||||
} catch (ex) {
|
||||
AST_Node.warn("Invalid array length: {length} [{file}:{line},{col}]", {
|
||||
length: length,
|
||||
@@ -7959,81 +7980,90 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (!first.is_boolean(compressor) && !first.is_string(compressor)) break;
|
||||
}
|
||||
return make_node(AST_Array, self, {
|
||||
elements: self.args
|
||||
});
|
||||
// Array(...) ---> [ ... ]
|
||||
return make_node(AST_Array, self, { elements: self.args });
|
||||
case "Object":
|
||||
if (self.args.length == 0) {
|
||||
return make_node(AST_Object, self, {
|
||||
properties: []
|
||||
});
|
||||
}
|
||||
// Object() ---> {}
|
||||
if (self.args.length == 0) return make_node(AST_Object, self, { properties: [] });
|
||||
break;
|
||||
case "String":
|
||||
if (self.args.length == 0) return make_node(AST_String, self, {
|
||||
value: ""
|
||||
});
|
||||
if (self.args.length <= 1) return make_node(AST_Binary, self, {
|
||||
left: self.args[0],
|
||||
// String() ---> ""
|
||||
if (self.args.length == 0) return make_node(AST_String, self, { value: "" });
|
||||
// String(x) ---> "" + x
|
||||
if (self.args.length == 1) return make_node(AST_Binary, self, {
|
||||
operator: "+",
|
||||
right: make_node(AST_String, self, { value: "" })
|
||||
left: make_node(AST_String, self, { value: "" }),
|
||||
right: self.args[0],
|
||||
}).optimize(compressor);
|
||||
break;
|
||||
case "Number":
|
||||
if (self.args.length == 0) return make_node(AST_Number, self, {
|
||||
value: 0
|
||||
});
|
||||
// Number() ---> 0
|
||||
if (self.args.length == 0) return make_node(AST_Number, self, { value: 0 });
|
||||
// Number(x) ---> +("" + x)
|
||||
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
|
||||
expression: self.args[0],
|
||||
operator: "+"
|
||||
}).optimize(compressor);
|
||||
case "Boolean":
|
||||
if (self.args.length == 0) return make_node(AST_False, self);
|
||||
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
|
||||
expression: make_node(AST_UnaryPrefix, self, {
|
||||
expression: self.args[0],
|
||||
operator: "!"
|
||||
operator: "+",
|
||||
expression: make_node(AST_Binary, self, {
|
||||
operator: "+",
|
||||
left: make_node(AST_String, self, { value: "" }),
|
||||
right: self.args[0],
|
||||
}),
|
||||
}).optimize(compressor);
|
||||
break;
|
||||
case "Boolean":
|
||||
// Boolean() ---> false
|
||||
if (self.args.length == 0) return make_node(AST_False, self).optimize(compressor);
|
||||
// Boolean(x) ---> !!x
|
||||
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: make_node(AST_UnaryPrefix, self, {
|
||||
operator: "!",
|
||||
expression: self.args[0],
|
||||
}),
|
||||
operator: "!"
|
||||
}).optimize(compressor);
|
||||
break;
|
||||
case "RegExp":
|
||||
// attempt to convert RegExp(...) to literal
|
||||
var params = [];
|
||||
if (all(self.args, function(arg) {
|
||||
var value = arg.evaluate(compressor);
|
||||
params.unshift(value);
|
||||
return arg !== value;
|
||||
})) {
|
||||
try {
|
||||
return best_of(compressor, self, make_node(AST_RegExp, self, {
|
||||
value: RegExp.apply(RegExp, params),
|
||||
}));
|
||||
} catch (ex) {
|
||||
AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
|
||||
expr: self,
|
||||
file: self.start.file,
|
||||
line: self.start.line,
|
||||
col: self.start.col,
|
||||
});
|
||||
}
|
||||
})) try {
|
||||
return best_of(compressor, self, make_node(AST_RegExp, self, {
|
||||
value: RegExp.apply(RegExp, params),
|
||||
}));
|
||||
} catch (ex) {
|
||||
AST_Node.warn("Error converting {expr} [{file}:{line},{col}]", {
|
||||
expr: self,
|
||||
file: self.start.file,
|
||||
line: self.start.line,
|
||||
col: self.start.col,
|
||||
});
|
||||
}
|
||||
break;
|
||||
} else if (exp instanceof AST_Dot) switch(exp.property) {
|
||||
} else if (exp instanceof AST_Dot) switch (exp.property) {
|
||||
case "toString":
|
||||
// x.toString() ---> "" + x
|
||||
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
|
||||
left: make_node(AST_String, self, { value: "" }),
|
||||
right: exp.expression,
|
||||
}).optimize(compressor);
|
||||
}
|
||||
break;
|
||||
case "join":
|
||||
if (exp.expression instanceof AST_Array) EXIT: {
|
||||
var separator;
|
||||
if (self.args.length > 0) {
|
||||
separator = self.args[0].evaluate(compressor);
|
||||
if (separator === self.args[0]) break EXIT; // not a constant
|
||||
if (exp.expression instanceof AST_Array && self.args.length < 2) EXIT: {
|
||||
var separator = self.args[0];
|
||||
// [].join() ---> ""
|
||||
// [].join(x) ---> (x, "")
|
||||
if (exp.expression.elements.length == 0) return separator ? make_sequence(self, [
|
||||
separator,
|
||||
make_node(AST_String, self, { value: "" }),
|
||||
]).optimize(compressor) : make_node(AST_String, self, { value: "" });
|
||||
if (separator) {
|
||||
separator = separator.evaluate(compressor);
|
||||
if (separator instanceof AST_Node) break EXIT; // not a constant
|
||||
}
|
||||
var elements = [];
|
||||
var consts = [];
|
||||
@@ -8044,45 +8074,46 @@ merge(Compressor.prototype, {
|
||||
} else {
|
||||
if (consts.length > 0) {
|
||||
elements.push(make_node(AST_String, self, {
|
||||
value: consts.join(separator)
|
||||
value: consts.join(separator),
|
||||
}));
|
||||
consts.length = 0;
|
||||
}
|
||||
elements.push(el);
|
||||
}
|
||||
});
|
||||
if (consts.length > 0) {
|
||||
elements.push(make_node(AST_String, self, {
|
||||
value: consts.join(separator)
|
||||
}));
|
||||
}
|
||||
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
|
||||
if (consts.length > 0) elements.push(make_node(AST_String, self, {
|
||||
value: consts.join(separator),
|
||||
}));
|
||||
// [ x ].join() ---> "" + x
|
||||
// [ x ].join(".") ---> "" + x
|
||||
// [ 1, 2, 3 ].join() ---> "1,2,3"
|
||||
// [ 1, 2, 3 ].join(".") ---> "1.2.3"
|
||||
if (elements.length == 1) {
|
||||
if (elements[0].is_string(compressor)) {
|
||||
return elements[0];
|
||||
}
|
||||
if (elements[0].is_string(compressor)) return elements[0];
|
||||
return make_node(AST_Binary, elements[0], {
|
||||
operator : "+",
|
||||
left : make_node(AST_String, self, { value: "" }),
|
||||
right : elements[0]
|
||||
operator: "+",
|
||||
left: make_node(AST_String, self, { value: "" }),
|
||||
right: elements[0],
|
||||
});
|
||||
}
|
||||
// [ 1, 2, a, 3 ].join("") ---> "12" + a + "3"
|
||||
if (separator == "") {
|
||||
var first;
|
||||
if (elements[0].is_string(compressor)
|
||||
|| elements[1].is_string(compressor)) {
|
||||
if (elements[0].is_string(compressor) || elements[1].is_string(compressor)) {
|
||||
first = elements.shift();
|
||||
} else {
|
||||
first = make_node(AST_String, self, { value: "" });
|
||||
}
|
||||
return elements.reduce(function(prev, el) {
|
||||
return make_node(AST_Binary, el, {
|
||||
operator : "+",
|
||||
left : prev,
|
||||
right : el
|
||||
operator: "+",
|
||||
left: prev,
|
||||
right: el,
|
||||
});
|
||||
}, first).optimize(compressor);
|
||||
}
|
||||
// [ x, "foo", "bar", y ].join() ---> [ x, "foo,bar", y ].join()
|
||||
// [ x, "foo", "bar", y ].join("-") ---> [ x, "foo-bar", y ].join("-")
|
||||
// need this awkward cloning to not affect original element
|
||||
// best_of will decide which one to get through.
|
||||
var node = self.clone();
|
||||
@@ -8146,7 +8177,7 @@ merge(Compressor.prototype, {
|
||||
if (compressor.option("unsafe_Function")
|
||||
&& is_undeclared_ref(exp)
|
||||
&& exp.name == "Function") {
|
||||
// new Function() => function(){}
|
||||
// new Function() ---> function(){}
|
||||
if (self.args.length == 0) return make_node(AST_Function, self, {
|
||||
argnames: [],
|
||||
body: []
|
||||
@@ -8160,7 +8191,7 @@ merge(Compressor.prototype, {
|
||||
try {
|
||||
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
|
||||
return arg.value;
|
||||
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
|
||||
}).join() + "){" + self.args[self.args.length - 1].value + "})";
|
||||
var ast = parse(code);
|
||||
var mangle = { ie8: compressor.option("ie8") };
|
||||
ast.figure_out_scope(mangle);
|
||||
@@ -8183,7 +8214,7 @@ merge(Compressor.prototype, {
|
||||
make_node(AST_String, self, {
|
||||
value: fun.argnames.map(function(arg) {
|
||||
return arg.print_to_string();
|
||||
}).join(",")
|
||||
}).join(),
|
||||
}),
|
||||
make_node(AST_String, self.args[self.args.length - 1], {
|
||||
value: code.get().replace(/^\{|\}$/g, "")
|
||||
@@ -8235,7 +8266,7 @@ merge(Compressor.prototype, {
|
||||
if (can_inline
|
||||
&& !fn.uses_arguments
|
||||
&& !fn.pinned()
|
||||
&& !(fn.name && fn instanceof AST_Function)
|
||||
&& !(fn.name && is_function(fn))
|
||||
&& (exp === fn || !recursive_ref(compressor, def = exp.definition())
|
||||
&& fn.is_constant_expression(find_scope(compressor)))
|
||||
&& !has_spread
|
||||
@@ -8774,8 +8805,8 @@ merge(Compressor.prototype, {
|
||||
return self;
|
||||
});
|
||||
|
||||
// (a = b, x && a = c) => a = x ? c : b
|
||||
// (a = b, x || a = c) => a = x ? b : c
|
||||
// (a = b, x && a = c) ---> a = x ? c : b
|
||||
// (a = b, x || a = c) ---> a = x ? b : c
|
||||
function to_conditional_assignment(compressor, def, value, node) {
|
||||
if (!(node instanceof AST_Binary)) return;
|
||||
if (!lazy_op[node.operator]) return;
|
||||
@@ -8906,7 +8937,7 @@ merge(Compressor.prototype, {
|
||||
} else if (compressor.in_boolean_context()) switch (op) {
|
||||
case "!":
|
||||
if (exp instanceof AST_UnaryPrefix && exp.operator == "!") {
|
||||
// !!foo => foo, if we're in boolean context
|
||||
// !!foo ---> foo, if we're in boolean context
|
||||
return exp.expression;
|
||||
}
|
||||
if (exp instanceof AST_Binary) {
|
||||
@@ -9071,8 +9102,8 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (compressor.option("assignments") && lazy_op[self.operator]) {
|
||||
var assign = self.right;
|
||||
// a || (a = x) => a = a || x
|
||||
// a && (a = x) => a = a && x
|
||||
// a || (a = x) ---> a = a || x
|
||||
// a && (a = x) ---> a = a && x
|
||||
if (self.left instanceof AST_SymbolRef
|
||||
&& assign instanceof AST_Assign
|
||||
&& assign.operator == "="
|
||||
@@ -9102,11 +9133,11 @@ merge(Compressor.prototype, {
|
||||
// XXX: intentionally falling down to the next case
|
||||
case "==":
|
||||
case "!=":
|
||||
// void 0 == x => null == x
|
||||
// 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
|
||||
// "undefined" == typeof x ---> undefined === x
|
||||
else if (compressor.option("typeofs")
|
||||
&& self.left instanceof AST_String
|
||||
&& self.left.value == "undefined"
|
||||
@@ -9120,7 +9151,7 @@ merge(Compressor.prototype, {
|
||||
if (self.operator.length == 2) self.operator += "=";
|
||||
}
|
||||
}
|
||||
// obj !== obj => false
|
||||
// obj !== obj ---> false
|
||||
else if (self.left instanceof AST_SymbolRef
|
||||
&& self.right instanceof AST_SymbolRef
|
||||
&& self.left.definition() === self.right.definition()
|
||||
@@ -9130,8 +9161,8 @@ merge(Compressor.prototype, {
|
||||
break;
|
||||
case "&&":
|
||||
case "||":
|
||||
// void 0 !== x && null !== x => null != x
|
||||
// void 0 === x || null === x => null == x
|
||||
// void 0 !== x && null !== x ---> null != x
|
||||
// void 0 === x || null === x ---> null == x
|
||||
var lhs = self.left;
|
||||
if (lhs.operator == self.operator) {
|
||||
lhs = lhs.right;
|
||||
@@ -9208,8 +9239,8 @@ merge(Compressor.prototype, {
|
||||
case ">=": reverse("<="); break;
|
||||
}
|
||||
}
|
||||
// x && (y && z) => x && y && z
|
||||
// x || (y || z) => x || y || z
|
||||
// x && (y && z) ---> x && y && z
|
||||
// x || (y || z) ---> x || y || z
|
||||
if (compressor.option("conditionals")
|
||||
&& lazy_op[self.operator]
|
||||
&& self.right instanceof AST_Binary
|
||||
@@ -9217,19 +9248,19 @@ merge(Compressor.prototype, {
|
||||
swap_chain();
|
||||
}
|
||||
if (compressor.option("strings") && self.operator == "+") {
|
||||
// "foo" + 42 + "" => "foo" + 42
|
||||
// "foo" + 42 + "" ---> "foo" + 42
|
||||
if (self.right instanceof AST_String
|
||||
&& self.right.value == ""
|
||||
&& self.left.is_string(compressor)) {
|
||||
return self.left.optimize(compressor);
|
||||
}
|
||||
// "" + ("foo" + 42) => "foo" + 42
|
||||
// "" + ("foo" + 42) ---> "foo" + 42
|
||||
if (self.left instanceof AST_String
|
||||
&& self.left.value == ""
|
||||
&& self.right.is_string(compressor)) {
|
||||
return self.right.optimize(compressor);
|
||||
}
|
||||
// "" + 42 + "foo" => 42 + "foo"
|
||||
// "" + 42 + "foo" ---> 42 + "foo"
|
||||
if (self.left instanceof AST_Binary
|
||||
&& self.left.operator == "+"
|
||||
&& self.left.left instanceof AST_String
|
||||
@@ -9238,8 +9269,8 @@ merge(Compressor.prototype, {
|
||||
self.left = self.left.right;
|
||||
return self.optimize(compressor);
|
||||
}
|
||||
// "x" + (y + "z") => "x" + y + "z"
|
||||
// x + ("y" + z) => x + "y" + z
|
||||
// "x" + (y + "z") ---> "x" + y + "z"
|
||||
// x + ("y" + z) ---> x + "y" + z
|
||||
if (self.right instanceof AST_Binary
|
||||
&& self.operator == self.right.operator
|
||||
&& (self.left.is_string(compressor) && self.right.is_string(compressor)
|
||||
@@ -9275,7 +9306,7 @@ merge(Compressor.prototype, {
|
||||
return self.left.optimize(compressor);
|
||||
}
|
||||
}
|
||||
// (x || false) && y => x ? y : false
|
||||
// (x || false) && y ---> x ? y : false
|
||||
if (self.left.operator == "||") {
|
||||
var lr = self.left.right.evaluate(compressor, true);
|
||||
if (!lr) return make_node(AST_Conditional, self, {
|
||||
@@ -9309,7 +9340,7 @@ merge(Compressor.prototype, {
|
||||
]).optimize(compressor);
|
||||
} else self.truthy = true;
|
||||
}
|
||||
// x && true || y => x ? true : y
|
||||
// x && true || y ---> x ? true : y
|
||||
if (self.left.operator == "&&") {
|
||||
var lr = self.left.right.is_truthy() || self.left.right.evaluate(compressor, true);
|
||||
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
|
||||
@@ -9320,7 +9351,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
break;
|
||||
case "+":
|
||||
// "foo" + ("bar" + x) => "foobar" + x
|
||||
// "foo" + ("bar" + x) ---> "foobar" + x
|
||||
if (self.left instanceof AST_Constant
|
||||
&& self.right instanceof AST_Binary
|
||||
&& self.right.operator == "+"
|
||||
@@ -9336,7 +9367,7 @@ merge(Compressor.prototype, {
|
||||
right: self.right.right
|
||||
});
|
||||
}
|
||||
// (x + "foo") + "bar" => x + "foobar"
|
||||
// (x + "foo") + "bar" ---> x + "foobar"
|
||||
if (self.right instanceof AST_Constant
|
||||
&& self.left instanceof AST_Binary
|
||||
&& self.left.operator == "+"
|
||||
@@ -9352,7 +9383,7 @@ merge(Compressor.prototype, {
|
||||
})
|
||||
});
|
||||
}
|
||||
// a + -b => a - b
|
||||
// a + -b ---> a - b
|
||||
if (self.right instanceof AST_UnaryPrefix
|
||||
&& self.right.operator == "-"
|
||||
&& self.left.is_number(compressor)) {
|
||||
@@ -9363,7 +9394,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
break;
|
||||
}
|
||||
// -a + b => b - a
|
||||
// -a + b ---> b - a
|
||||
if (self.left instanceof AST_UnaryPrefix
|
||||
&& self.left.operator == "-"
|
||||
&& reversible()
|
||||
@@ -9375,7 +9406,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
break;
|
||||
}
|
||||
// (a + b) + 3 => 3 + (a + b)
|
||||
// (a + b) + 3 ---> 3 + (a + b)
|
||||
if (compressor.option("unsafe_math")
|
||||
&& self.left instanceof AST_Binary
|
||||
&& PRECEDENCE[self.left.operator] == PRECEDENCE[self.operator]
|
||||
@@ -9396,7 +9427,7 @@ merge(Compressor.prototype, {
|
||||
break;
|
||||
}
|
||||
case "-":
|
||||
// a - -b => a + b
|
||||
// a - -b ---> a + b
|
||||
if (self.right instanceof AST_UnaryPrefix
|
||||
&& self.right.operator == "-"
|
||||
&& self.left.is_number(compressor)
|
||||
@@ -9411,8 +9442,8 @@ merge(Compressor.prototype, {
|
||||
case "*":
|
||||
case "/":
|
||||
associative = compressor.option("unsafe_math");
|
||||
// +a - b => a - b
|
||||
// a - +b => a - b
|
||||
// +a - b ---> a - b
|
||||
// a - +b ---> a - b
|
||||
if (self.operator != "+") [ "left", "right" ].forEach(function(operand) {
|
||||
var node = self[operand];
|
||||
if (node instanceof AST_UnaryPrefix && node.operator == "+") {
|
||||
@@ -9425,7 +9456,7 @@ merge(Compressor.prototype, {
|
||||
case "&":
|
||||
case "|":
|
||||
case "^":
|
||||
// a + +b => +b + a
|
||||
// a + +b ---> +b + a
|
||||
if (self.operator != "-"
|
||||
&& self.operator != "/"
|
||||
&& (self.left.is_boolean(compressor) || self.left.is_number(compressor))
|
||||
@@ -9447,7 +9478,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
}
|
||||
if (!associative || !self.is_number(compressor)) break;
|
||||
// a + (b + c) => (a + b) + c
|
||||
// a + (b + c) ---> (a + b) + c
|
||||
if (self.right instanceof AST_Binary
|
||||
&& self.right.operator != "%"
|
||||
&& PRECEDENCE[self.right.operator] == PRECEDENCE[self.operator]
|
||||
@@ -9479,8 +9510,8 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
}
|
||||
// (2 * n) * 3 => 6 * n
|
||||
// (n + 2) + 3 => n + 5
|
||||
// (2 * n) * 3 ---> 6 * n
|
||||
// (n + 2) + 3 ---> n + 5
|
||||
if (self.right instanceof AST_Constant
|
||||
&& self.left instanceof AST_Binary
|
||||
&& self.left.operator != "%"
|
||||
@@ -9505,7 +9536,7 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (!(parent instanceof AST_UnaryPrefix && parent.operator == "delete")) {
|
||||
if (self.left instanceof AST_Number && !self.right.is_constant()) switch (self.operator) {
|
||||
// 0 + n => n
|
||||
// 0 + n ---> n
|
||||
case "+":
|
||||
if (self.left.value == 0) {
|
||||
if (self.right.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
|
||||
@@ -9515,7 +9546,7 @@ merge(Compressor.prototype, {
|
||||
if (self.right.is_number(compressor) && !self.right.is_negative_zero()) return self.right;
|
||||
}
|
||||
break;
|
||||
// 1 * n => n
|
||||
// 1 * n ---> n
|
||||
case "*":
|
||||
if (self.left.value == 1) {
|
||||
return self.right.is_number(compressor) ? self.right : make_node(AST_UnaryPrefix, self, {
|
||||
@@ -9526,7 +9557,7 @@ merge(Compressor.prototype, {
|
||||
break;
|
||||
}
|
||||
if (self.right instanceof AST_Number && !self.left.is_constant()) switch (self.operator) {
|
||||
// n + 0 => n
|
||||
// n + 0 ---> n
|
||||
case "+":
|
||||
if (self.right.value == 0) {
|
||||
if (self.left.is_boolean(compressor)) return make_node(AST_UnaryPrefix, self, {
|
||||
@@ -9536,7 +9567,7 @@ merge(Compressor.prototype, {
|
||||
if (self.left.is_number(compressor) && !self.left.is_negative_zero()) return self.left;
|
||||
}
|
||||
break;
|
||||
// n - 0 => n
|
||||
// n - 0 ---> n
|
||||
case "-":
|
||||
if (self.right.value == 0) {
|
||||
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
|
||||
@@ -9545,7 +9576,7 @@ merge(Compressor.prototype, {
|
||||
}).optimize(compressor);
|
||||
}
|
||||
break;
|
||||
// n / 1 => n
|
||||
// n / 1 ---> n
|
||||
case "/":
|
||||
if (self.right.value == 1) {
|
||||
return self.left.is_number(compressor) ? self.left : make_node(AST_UnaryPrefix, self, {
|
||||
@@ -9668,16 +9699,16 @@ merge(Compressor.prototype, {
|
||||
function is_indexOf_match_pattern() {
|
||||
switch (self.operator) {
|
||||
case "<=":
|
||||
// 0 <= array.indexOf(string) => !!~array.indexOf(string)
|
||||
// 0 <= array.indexOf(string) ---> !!~array.indexOf(string)
|
||||
return indexRight && self.left instanceof AST_Number && self.left.value == 0;
|
||||
case "<":
|
||||
// array.indexOf(string) < 0 => !~array.indexOf(string)
|
||||
// array.indexOf(string) < 0 ---> !~array.indexOf(string)
|
||||
if (indexLeft && self.right instanceof AST_Number && self.right.value == 0) return true;
|
||||
// -1 < array.indexOf(string) => !!~array.indexOf(string)
|
||||
// -1 < array.indexOf(string) ---> !!~array.indexOf(string)
|
||||
case "==":
|
||||
case "!=":
|
||||
// -1 == array.indexOf(string) => !~array.indexOf(string)
|
||||
// -1 != array.indexOf(string) => !!~array.indexOf(string)
|
||||
// -1 == array.indexOf(string) ---> !~array.indexOf(string)
|
||||
// -1 != array.indexOf(string) ---> !!~array.indexOf(string)
|
||||
if (!indexRight) return false;
|
||||
return self.left instanceof AST_Number && self.left.value == -1
|
||||
|| self.left instanceof AST_UnaryPrefix && self.left.operator == "-"
|
||||
@@ -10031,7 +10062,7 @@ merge(Compressor.prototype, {
|
||||
if (self.right.left instanceof AST_SymbolRef
|
||||
&& self.right.left.name == self.left.name
|
||||
&& ASSIGN_OPS[self.right.operator]) {
|
||||
// x = x - 2 => x -= 2
|
||||
// x = x - 2 ---> x -= 2
|
||||
return make_node(AST_Assign, self, {
|
||||
operator: self.right.operator + "=",
|
||||
left: self.left,
|
||||
@@ -10042,7 +10073,7 @@ merge(Compressor.prototype, {
|
||||
&& self.right.right.name == self.left.name
|
||||
&& ASSIGN_OPS_COMMUTATIVE[self.right.operator]
|
||||
&& !self.right.left.has_side_effects(compressor)) {
|
||||
// x = 2 & x => x &= 2
|
||||
// x = 2 & x ---> x &= 2
|
||||
return make_node(AST_Assign, self, {
|
||||
operator: self.right.operator + "=",
|
||||
left: self.left,
|
||||
@@ -10116,13 +10147,13 @@ merge(Compressor.prototype, {
|
||||
var consequent = self.consequent;
|
||||
var alternative = self.alternative;
|
||||
if (repeatable(compressor, condition)) {
|
||||
// x ? x : y => x || y
|
||||
// x ? x : y ---> x || y
|
||||
if (condition.equivalent_to(consequent)) return make_node(AST_Binary, self, {
|
||||
operator: "||",
|
||||
left: condition,
|
||||
right: alternative,
|
||||
}).optimize(compressor);
|
||||
// x ? y : x => x && y
|
||||
// x ? y : x ---> x && y
|
||||
if (condition.equivalent_to(alternative)) return make_node(AST_Binary, self, {
|
||||
operator: "&&",
|
||||
left: condition,
|
||||
@@ -10156,17 +10187,17 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
}
|
||||
// x ? y : y => x, y
|
||||
// x ? y : y ---> x, y
|
||||
if (consequent.equivalent_to(alternative)) return make_sequence(self, [
|
||||
condition,
|
||||
consequent
|
||||
]).optimize(compressor);
|
||||
// x ? y.p : z.p => (x ? y : z).p
|
||||
// x ? y(a) : z(a) => (x ? y : z)(a)
|
||||
// x ? y.f(a) : z.f(a) => (x ? y : z).f(a)
|
||||
// x ? y.p : z.p ---> (x ? y : z).p
|
||||
// x ? y(a) : z(a) ---> (x ? y : z)(a)
|
||||
// x ? y.f(a) : z.f(a) ---> (x ? y : z).f(a)
|
||||
var combined = combine_tail(consequent, alternative, true);
|
||||
if (combined) return combined;
|
||||
// x ? y(a) : y(b) => y(x ? a : b)
|
||||
// x ? y(a) : y(b) ---> y(x ? a : b)
|
||||
var arg_index;
|
||||
if (consequent instanceof AST_Call
|
||||
&& alternative.TYPE == consequent.TYPE
|
||||
@@ -10189,7 +10220,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
return node;
|
||||
}
|
||||
// x ? (y ? a : b) : b => x && y ? a : b
|
||||
// x ? (y ? a : b) : b ---> x && y ? a : b
|
||||
if (consequent instanceof AST_Conditional
|
||||
&& consequent.alternative.equivalent_to(alternative)) {
|
||||
return make_node(AST_Conditional, self, {
|
||||
@@ -10202,7 +10233,7 @@ merge(Compressor.prototype, {
|
||||
alternative: alternative
|
||||
});
|
||||
}
|
||||
// x ? (y ? a : b) : a => !x || y ? a : b
|
||||
// x ? (y ? a : b) : a ---> !x || y ? a : b
|
||||
if (consequent instanceof AST_Conditional
|
||||
&& consequent.consequent.equivalent_to(alternative)) {
|
||||
return make_node(AST_Conditional, self, {
|
||||
@@ -10215,7 +10246,7 @@ merge(Compressor.prototype, {
|
||||
alternative: consequent.alternative
|
||||
});
|
||||
}
|
||||
// x ? a : (y ? a : b) => x || y ? a : b
|
||||
// x ? a : (y ? a : b) ---> x || y ? a : b
|
||||
if (alternative instanceof AST_Conditional
|
||||
&& consequent.equivalent_to(alternative.consequent)) {
|
||||
return make_node(AST_Conditional, self, {
|
||||
@@ -10228,7 +10259,7 @@ merge(Compressor.prototype, {
|
||||
alternative: alternative.alternative
|
||||
});
|
||||
}
|
||||
// x ? b : (y ? a : b) => !x && y ? a : b
|
||||
// x ? b : (y ? a : b) ---> !x && y ? a : b
|
||||
if (alternative instanceof AST_Conditional
|
||||
&& consequent.equivalent_to(alternative.alternative)) {
|
||||
return make_node(AST_Conditional, self, {
|
||||
@@ -10241,7 +10272,7 @@ merge(Compressor.prototype, {
|
||||
alternative: consequent
|
||||
});
|
||||
}
|
||||
// x ? (a, c) : (b, c) => x ? a : b, c
|
||||
// x ? (a, c) : (b, c) ---> x ? a : b, c
|
||||
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
|
||||
&& consequent.tail_node().equivalent_to(alternative.tail_node())) {
|
||||
return make_sequence(self, [
|
||||
@@ -10253,7 +10284,7 @@ merge(Compressor.prototype, {
|
||||
consequent.tail_node()
|
||||
]).optimize(compressor);
|
||||
}
|
||||
// x ? y && a : a => (!x || y) && a
|
||||
// x ? y && a : a ---> (!x || y) && a
|
||||
if (consequent instanceof AST_Binary
|
||||
&& consequent.operator == "&&"
|
||||
&& consequent.right.equivalent_to(alternative)) {
|
||||
@@ -10267,7 +10298,7 @@ merge(Compressor.prototype, {
|
||||
right: alternative
|
||||
}).optimize(compressor);
|
||||
}
|
||||
// x ? y || a : a => x && y || a
|
||||
// x ? y || a : a ---> x && y || a
|
||||
if (consequent instanceof AST_Binary
|
||||
&& consequent.operator == "||"
|
||||
&& consequent.right.equivalent_to(alternative)) {
|
||||
@@ -10281,7 +10312,7 @@ merge(Compressor.prototype, {
|
||||
right: alternative
|
||||
}).optimize(compressor);
|
||||
}
|
||||
// x ? a : y && a => (x || y) && a
|
||||
// x ? a : y && a ---> (x || y) && a
|
||||
if (alternative instanceof AST_Binary
|
||||
&& alternative.operator == "&&"
|
||||
&& alternative.right.equivalent_to(consequent)) {
|
||||
@@ -10295,7 +10326,7 @@ merge(Compressor.prototype, {
|
||||
right: consequent
|
||||
}).optimize(compressor);
|
||||
}
|
||||
// x ? a : y || a => !x && y || a
|
||||
// x ? a : y || a ---> !x && y || a
|
||||
if (alternative instanceof AST_Binary
|
||||
&& alternative.operator == "||"
|
||||
&& alternative.right.equivalent_to(consequent)) {
|
||||
@@ -10312,10 +10343,10 @@ merge(Compressor.prototype, {
|
||||
var in_bool = compressor.option("booleans") && compressor.in_boolean_context();
|
||||
if (is_true(consequent)) {
|
||||
if (is_false(alternative)) {
|
||||
// c ? true : false => !!c
|
||||
// c ? true : false ---> !!c
|
||||
return booleanize(condition);
|
||||
}
|
||||
// c ? true : x => !!c || x
|
||||
// c ? true : x ---> !!c || x
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: "||",
|
||||
left: booleanize(condition),
|
||||
@@ -10324,10 +10355,10 @@ merge(Compressor.prototype, {
|
||||
}
|
||||
if (is_false(consequent)) {
|
||||
if (is_true(alternative)) {
|
||||
// c ? false : true => !c
|
||||
// c ? false : true ---> !c
|
||||
return booleanize(condition.negate(compressor));
|
||||
}
|
||||
// c ? false : x => !c && x
|
||||
// c ? false : x ---> !c && x
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: "&&",
|
||||
left: booleanize(condition.negate(compressor)),
|
||||
@@ -10335,7 +10366,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
if (is_true(alternative)) {
|
||||
// c ? x : true => !c || x
|
||||
// c ? x : true ---> !c || x
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: "||",
|
||||
left: booleanize(condition.negate(compressor)),
|
||||
@@ -10343,7 +10374,7 @@ merge(Compressor.prototype, {
|
||||
});
|
||||
}
|
||||
if (is_false(alternative)) {
|
||||
// c ? x : false => !!c && x
|
||||
// c ? x : false ---> !!c && x
|
||||
return make_node(AST_Binary, self, {
|
||||
operator: "&&",
|
||||
left: booleanize(condition),
|
||||
@@ -10846,7 +10877,7 @@ merge(Compressor.prototype, {
|
||||
flush();
|
||||
values.push(prop);
|
||||
}
|
||||
if (found && !generated && typeof key == "string" && /^[1-9]*[0-9]$/.test(key)) {
|
||||
if (found && !generated && typeof key == "string" && RE_POSITIVE_INTEGER.test(key)) {
|
||||
generated = true;
|
||||
if (keys.has(key)) prop = keys.get(key)[0];
|
||||
prop.key = make_node(AST_Number, prop, {
|
||||
|
||||
@@ -684,37 +684,41 @@ function OutputStream(options) {
|
||||
|
||||
PARENS(AST_Unary, function(output) {
|
||||
var p = output.parent();
|
||||
// (-x) ** y
|
||||
if (p instanceof AST_Binary) return p.operator == "**" && p.left === this;
|
||||
// (x++).toString(3)
|
||||
// (typeof x).length
|
||||
return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this;
|
||||
});
|
||||
|
||||
PARENS(AST_Sequence, function(output) {
|
||||
var p = output.parent();
|
||||
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
|
||||
// [ 1, (2, 3), 4 ] ---> [ 1, 3, 4 ]
|
||||
return p instanceof AST_Array
|
||||
// () => (foo, bar)
|
||||
// () ---> (foo, bar)
|
||||
|| is_arrow(p) && p.value === this
|
||||
// await (foo, bar)
|
||||
|| p instanceof AST_Await
|
||||
// 1 + (2, 3) + 4 ==> 8
|
||||
// 1 + (2, 3) + 4 ---> 8
|
||||
|| p instanceof AST_Binary
|
||||
// new (foo, bar) or foo(1, (2, 3), 4)
|
||||
|| p instanceof AST_Call
|
||||
// (false, true) ? (a = 10, b = 20) : (c = 30)
|
||||
// ==> 20 (side effect, set a := 10 and b := 20)
|
||||
// ---> 20 (side effect, set a := 10 and b := 20)
|
||||
|| p instanceof AST_Conditional
|
||||
// [ a = (1, 2) ] = [] ==> a == 2
|
||||
// [ a = (1, 2) ] = [] ---> a == 2
|
||||
|| p instanceof AST_DefaultValue
|
||||
// { [(1, 2)]: 3 }[2] ==> 3
|
||||
// { foo: (1, 2) }.foo ==> 2
|
||||
// { [(1, 2)]: 3 }[2] ---> 3
|
||||
// { foo: (1, 2) }.foo ---> 2
|
||||
|| p instanceof AST_DestructuredKeyVal
|
||||
|| p instanceof AST_ObjectProperty
|
||||
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|
||||
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ---> 2
|
||||
|| p instanceof AST_PropAccess && p.expression === this
|
||||
// ...(foo, bar, baz)
|
||||
|| p instanceof AST_Spread
|
||||
// !(foo, bar, baz)
|
||||
|| p instanceof AST_Unary
|
||||
// var a = (1, 2), b = a + a; ==> b == 4
|
||||
// var a = (1, 2), b = a + a; ---> b == 4
|
||||
|| p instanceof AST_VarDef;
|
||||
});
|
||||
|
||||
@@ -722,11 +726,14 @@ function OutputStream(options) {
|
||||
var p = output.parent();
|
||||
// await (foo && bar)
|
||||
if (p instanceof AST_Await) return true;
|
||||
// this deals with precedence: 3 * (2 + 1)
|
||||
// this deals with precedence:
|
||||
// 3 * (2 + 1)
|
||||
// 3 - (2 - 1)
|
||||
// (1 ** 2) ** 3
|
||||
if (p instanceof AST_Binary) {
|
||||
var po = p.operator, pp = PRECEDENCE[po];
|
||||
var so = this.operator, sp = PRECEDENCE[so];
|
||||
return pp > sp || (pp == sp && this === p.right);
|
||||
return pp > sp || (pp == sp && this === p[po == "**" ? "left" : "right"]);
|
||||
}
|
||||
// (foo && bar)()
|
||||
if (p instanceof AST_Call) return p.expression === this;
|
||||
@@ -777,14 +784,10 @@ function OutputStream(options) {
|
||||
});
|
||||
|
||||
PARENS(AST_Number, function(output) {
|
||||
if (!output.option("galio")) return false;
|
||||
// https://github.com/mishoo/UglifyJS/pull/1009
|
||||
var p = output.parent();
|
||||
if (p instanceof AST_PropAccess && p.expression === this) {
|
||||
var value = this.value;
|
||||
// https://github.com/mishoo/UglifyJS/issues/115
|
||||
return value < 0
|
||||
// https://github.com/mishoo/UglifyJS/pull/1009
|
||||
|| output.option("galio") && /^0/.test(make_num(value));
|
||||
}
|
||||
return p instanceof AST_PropAccess && p.expression === this && /^0/.test(make_num(this.value));
|
||||
});
|
||||
|
||||
function needs_parens_assign_cond(self, output) {
|
||||
@@ -807,8 +810,8 @@ function OutputStream(options) {
|
||||
});
|
||||
PARENS(AST_Assign, function(output) {
|
||||
if (needs_parens_assign_cond(this, output)) return true;
|
||||
// v8 parser bug => workaround
|
||||
// f([1], [a] = []) => f([1], ([a] = []))
|
||||
// v8 parser bug ---> workaround
|
||||
// f([1], [a] = []) ---> f([1], ([a] = []))
|
||||
if (output.option("v8")) return this.left instanceof AST_Destructured;
|
||||
// ({ p: a } = o);
|
||||
if (this.left instanceof AST_DestructuredObject) return needs_parens_obj(output);
|
||||
@@ -822,6 +825,8 @@ function OutputStream(options) {
|
||||
|
||||
PARENS(AST_Await, function(output) {
|
||||
var p = output.parent();
|
||||
// (await x) ** y
|
||||
if (p instanceof AST_Binary) return p.operator == "**" && p.left === this;
|
||||
// new (await foo)
|
||||
// (await foo)(bar)
|
||||
if (p instanceof AST_Call) return p.expression === this;
|
||||
|
||||
28
lib/parse.js
28
lib/parse.js
@@ -81,6 +81,7 @@ var OPERATORS = makePredicate([
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
"**",
|
||||
">>",
|
||||
"<<",
|
||||
">>>",
|
||||
@@ -280,9 +281,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
||||
}
|
||||
|
||||
function read_while(pred) {
|
||||
var ret = "", ch, i = 0;
|
||||
while ((ch = peek()) && pred(ch, i++))
|
||||
ret += next();
|
||||
var ret = "", ch;
|
||||
while ((ch = peek()) && pred(ch)) ret += next();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -292,16 +292,14 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
||||
|
||||
function read_num(prefix) {
|
||||
var has_e = false, after_e = false, has_x = false, has_dot = prefix == ".";
|
||||
var num = read_while(function(ch, i) {
|
||||
var num = read_while(function(ch) {
|
||||
var code = ch.charCodeAt(0);
|
||||
switch (code) {
|
||||
case 120: case 88: // xX
|
||||
return has_x ? false : (has_x = true);
|
||||
case 101: case 69: // eE
|
||||
return has_x ? true : has_e ? false : (has_e = after_e = true);
|
||||
case 45: // -
|
||||
return after_e || (i == 0 && !prefix);
|
||||
case 43: // +
|
||||
case 43: case 45: // +-
|
||||
return after_e;
|
||||
case (after_e = false, 46): // .
|
||||
return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false;
|
||||
@@ -315,8 +313,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
|
||||
num = num.replace(has_x ? /([1-9a-f]|.0)_(?=[0-9a-f])/gi : /([1-9]|.0)_(?=[0-9])/gi, "$1");
|
||||
}
|
||||
var valid = parse_js_number(num);
|
||||
if (!isNaN(valid)) return token("num", valid);
|
||||
parse_error("Invalid syntax: " + num);
|
||||
if (isNaN(valid)) parse_error("Invalid syntax: " + num);
|
||||
if (has_dot || has_e || peek() != "n") return token("num", valid);
|
||||
return token("bigint", num.toLowerCase() + next());
|
||||
}
|
||||
|
||||
function read_escaped_char(in_string) {
|
||||
@@ -632,10 +631,11 @@ var PRECEDENCE = function(a, ret) {
|
||||
["<", ">", "<=", ">=", "in", "instanceof"],
|
||||
[">>", "<<", ">>>"],
|
||||
["+", "-"],
|
||||
["*", "/", "%"]
|
||||
["*", "/", "%"],
|
||||
["**"],
|
||||
], {});
|
||||
|
||||
var ATOMIC_START_TOKEN = makePredicate("atom num regexp string");
|
||||
var ATOMIC_START_TOKEN = makePredicate("atom bigint num regexp string");
|
||||
|
||||
/* -----[ Parser ]----- */
|
||||
|
||||
@@ -783,6 +783,7 @@ function parse($TEXT, options) {
|
||||
semicolon();
|
||||
return dir ? new AST_Directive(body) : new AST_SimpleStatement({ body: body });
|
||||
case "num":
|
||||
case "bigint":
|
||||
case "regexp":
|
||||
case "operator":
|
||||
case "atom":
|
||||
@@ -1361,6 +1362,9 @@ function parse($TEXT, options) {
|
||||
case "num":
|
||||
ret = new AST_Number({ start: tok, end: tok, value: tok.value });
|
||||
break;
|
||||
case "bigint":
|
||||
ret = new AST_BigInt({ start: tok, end: tok, value: tok.value });
|
||||
break;
|
||||
case "string":
|
||||
ret = new AST_String({
|
||||
start : tok,
|
||||
@@ -1858,7 +1862,7 @@ function parse($TEXT, options) {
|
||||
var prec = op != null ? PRECEDENCE[op] : null;
|
||||
if (prec != null && prec > min_prec) {
|
||||
next();
|
||||
var right = expr_op(maybe_await(), prec, no_in);
|
||||
var right = expr_op(maybe_await(), op == "**" ? prec - 1 : prec, no_in);
|
||||
return expr_op(new AST_Binary({
|
||||
start : left.start,
|
||||
left : left,
|
||||
|
||||
@@ -228,7 +228,7 @@ function mangle_properties(ast, options) {
|
||||
var mangled = cache.get(name);
|
||||
if (!mangled) {
|
||||
if (debug) {
|
||||
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
|
||||
// debug mode: use a prefix and suffix to preserve readability, e.g. o.foo ---> o._$foo$NNN_.
|
||||
var debug_mangled = "_$" + name + "$" + debug_suffix + "_";
|
||||
if (can_mangle(debug_mangled)) mangled = debug_mangled;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "JavaScript parser, mangler/compressor and beautifier toolkit",
|
||||
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
|
||||
"license": "BSD-2-Clause",
|
||||
"version": "3.12.5",
|
||||
"version": "3.12.6",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
},
|
||||
|
||||
@@ -209,7 +209,7 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
|
||||
} else {
|
||||
var toplevel = sandbox.has_toplevel(options);
|
||||
var expected = stdout[toplevel ? 1 : 0];
|
||||
var actual = run_code(result.code, toplevel);
|
||||
var actual = sandbox.run_code(result.code, toplevel);
|
||||
if (typeof expected != "string" && typeof actual != "string" && expected.name == actual.name) {
|
||||
actual = expected;
|
||||
}
|
||||
@@ -244,11 +244,6 @@ function reminify(orig_options, input_code, input_formatted, stdout) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function run_code(code, toplevel) {
|
||||
var result = sandbox.run_code(code, toplevel);
|
||||
return typeof result == "string" ? result.replace(/\u001b\[\d+m/g, "") : result;
|
||||
}
|
||||
|
||||
function test_case(test) {
|
||||
log(" Running test [{name}]", { name: test.name });
|
||||
U.AST_Node.enable_validation();
|
||||
@@ -380,7 +375,7 @@ function test_case(test) {
|
||||
}
|
||||
}
|
||||
if (test.expect_stdout && (!test.node_version || semver.satisfies(process.version, test.node_version))) {
|
||||
var stdout = [ run_code(input_code), run_code(input_code, true) ];
|
||||
var stdout = [ sandbox.run_code(input_code), sandbox.run_code(input_code, true) ];
|
||||
var toplevel = sandbox.has_toplevel({
|
||||
compress: test.options,
|
||||
mangle: test.mangle
|
||||
@@ -409,7 +404,7 @@ function test_case(test) {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
actual = run_code(output_code, toplevel);
|
||||
actual = sandbox.run_code(output_code, toplevel);
|
||||
if (!sandbox.same_stdout(test.expect_stdout, actual)) {
|
||||
log([
|
||||
"!!! failed",
|
||||
|
||||
@@ -13,9 +13,10 @@ holes_and_undefined: {
|
||||
}
|
||||
}
|
||||
|
||||
constant_join: {
|
||||
constant_join_1: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
side_effects: true,
|
||||
strings: true,
|
||||
unsafe: true,
|
||||
}
|
||||
@@ -57,7 +58,7 @@ constant_join: {
|
||||
var c5 = [ boo() + bar() + "foo", 1, 2, 3, "bar", bar() + "foo" ].join();
|
||||
var c6 = [ "1,2,,,foo,bar", baz() ].join();
|
||||
var d = "foo-3bar-baz";
|
||||
var e = [].join(foo + bar);
|
||||
var e = (foo, bar, "");
|
||||
var f = "";
|
||||
var g = "";
|
||||
}
|
||||
|
||||
@@ -612,22 +612,32 @@ issue_4340: {
|
||||
call_expression: {
|
||||
input: {
|
||||
console.log(typeof async function(log) {
|
||||
(await log)("FAIL");
|
||||
(await log)("foo");
|
||||
}(console.log).then);
|
||||
console.log("bar");
|
||||
}
|
||||
expect_exact: 'console.log(typeof async function(log){(await log)("FAIL")}(console.log).then);'
|
||||
expect_stdout: "function"
|
||||
expect_exact: 'console.log(typeof async function(log){(await log)("foo")}(console.log).then);console.log("bar");'
|
||||
expect_stdout: [
|
||||
"function",
|
||||
"bar",
|
||||
"foo",
|
||||
]
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
property_access_expression: {
|
||||
input: {
|
||||
console.log(typeof async function(con) {
|
||||
(await con).log("FAIL");
|
||||
(await con).log("foo");
|
||||
}(console).then);
|
||||
console.log("bar");
|
||||
}
|
||||
expect_exact: 'console.log(typeof async function(con){(await con).log("FAIL")}(console).then);'
|
||||
expect_stdout: "function"
|
||||
expect_exact: 'console.log(typeof async function(con){(await con).log("foo")}(console).then);console.log("bar");'
|
||||
expect_stdout: [
|
||||
"function",
|
||||
"bar",
|
||||
"foo",
|
||||
]
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
@@ -685,20 +695,18 @@ reduce_iife_3: {
|
||||
input: {
|
||||
var a = "foo";
|
||||
(async function() {
|
||||
console.log(a);
|
||||
console.log(await a);
|
||||
console.log(a, await a, a, await a);
|
||||
})();
|
||||
a = "bar";
|
||||
}
|
||||
expect: {
|
||||
var a = "foo";
|
||||
(async function() {
|
||||
console.log(a);
|
||||
console.log(await a);
|
||||
console.log(a, await a, a, await a);
|
||||
})();
|
||||
a = "bar";
|
||||
}
|
||||
expect_stdout: "foo"
|
||||
expect_stdout: "foo foo bar bar"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
@@ -995,3 +1003,64 @@ issue_4534: {
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
issue_4581: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var a = "PASS";
|
||||
(async () => (A, a = "FAIL"))();
|
||||
console.log(a);
|
||||
}
|
||||
expect: {
|
||||
var a = "PASS";
|
||||
(async () => (A, a = "FAIL"))();
|
||||
console.log(a);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
issue_4595: {
|
||||
options = {
|
||||
awaits: true,
|
||||
inline: true,
|
||||
}
|
||||
input: {
|
||||
(async function() {
|
||||
await async function f() {
|
||||
console.log(f.length);
|
||||
}();
|
||||
})();
|
||||
}
|
||||
expect: {
|
||||
(async function() {
|
||||
await async function f() {
|
||||
console.log(f.length);
|
||||
}();
|
||||
})();
|
||||
}
|
||||
expect_stdout: "0"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
issue_4598: {
|
||||
options = {
|
||||
conditionals: true,
|
||||
}
|
||||
input: {
|
||||
if (console.log("PASS")) {
|
||||
async function f() {}
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
async function f() {}
|
||||
console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
62
test/compress/bigint.js
Normal file
62
test/compress/bigint.js
Normal file
@@ -0,0 +1,62 @@
|
||||
arithmetic: {
|
||||
input: {
|
||||
console.log(((1n + 0x2n) * (0o3n - -4n)) >> (5n - 6n));
|
||||
}
|
||||
expect_exact: "console.log((1n+0x2n)*(0o3n- -4n)>>5n-6n);"
|
||||
expect_stdout: "42n"
|
||||
node_version: ">=10"
|
||||
}
|
||||
|
||||
minus_dot: {
|
||||
input: {
|
||||
console.log(typeof -42n.toString(), typeof (-42n).toString());
|
||||
}
|
||||
expect_exact: "console.log(typeof-42n.toString(),typeof(-42n).toString());"
|
||||
expect_stdout: "number string"
|
||||
node_version: ">=10"
|
||||
}
|
||||
|
||||
evaluate: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
console.log((0xDEAD_BEEFn).toString(16));
|
||||
}
|
||||
expect: {
|
||||
console.log(0xdeadbeefn.toString(16));
|
||||
}
|
||||
expect_stdout: "deadbeef"
|
||||
node_version: ">=10"
|
||||
}
|
||||
|
||||
Number: {
|
||||
options = {
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
console.log(Number(-0xfeed_dead_beef_badn));
|
||||
}
|
||||
expect: {
|
||||
console.log(+("" + -0xfeed_dead_beef_badn));
|
||||
}
|
||||
expect_stdout: "-1148098955808013200"
|
||||
node_version: ">=10"
|
||||
}
|
||||
|
||||
issue_4590: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
A = 1;
|
||||
0n || console.log("PASS");
|
||||
}
|
||||
expect: {
|
||||
A = 1;
|
||||
0n || console.log("PASS");
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=10"
|
||||
}
|
||||
@@ -5608,6 +5608,7 @@ collapse_rhs_array: {
|
||||
collapse_rhs_boolean_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
var a, b;
|
||||
@@ -5633,6 +5634,7 @@ collapse_rhs_boolean_1: {
|
||||
collapse_rhs_boolean_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
var a;
|
||||
@@ -5667,6 +5669,7 @@ collapse_rhs_boolean_3: {
|
||||
booleans: true,
|
||||
collapse_vars: true,
|
||||
conditionals: true,
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
var a, f, g, h, i, n, s, t, x, y;
|
||||
@@ -5720,6 +5723,7 @@ collapse_rhs_function: {
|
||||
collapse_rhs_number: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
var a, b;
|
||||
@@ -5799,6 +5803,7 @@ collapse_rhs_regexp: {
|
||||
collapse_rhs_string: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
var a, b;
|
||||
@@ -8705,3 +8710,48 @@ collapse_or_assign: {
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_4586_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = 42;
|
||||
(function f(b) {
|
||||
var b = a;
|
||||
if (b === arguments[0])
|
||||
console.log("PASS");
|
||||
})(console);
|
||||
}
|
||||
expect: {
|
||||
var a = 42;
|
||||
(function f(b) {
|
||||
var b = a;
|
||||
if (b === arguments[0])
|
||||
console.log("PASS");
|
||||
})(console);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
issue_4586_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
}
|
||||
input: {
|
||||
var a = 42;
|
||||
(function f(b) {
|
||||
b = a;
|
||||
if (b === arguments[0])
|
||||
console.log("PASS");
|
||||
})(console);
|
||||
}
|
||||
expect: {
|
||||
var a = 42;
|
||||
(function f(b) {
|
||||
if ((b = a) === arguments[0])
|
||||
console.log("PASS");
|
||||
})(console);
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ process_boolean_returns: {
|
||||
collapse_value_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
keep_fargs: false,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
@@ -169,7 +170,7 @@ collapse_value_1: {
|
||||
}());
|
||||
}
|
||||
expect: {
|
||||
console.log(function(a) {
|
||||
console.log(function() {
|
||||
return "PASS";
|
||||
}());
|
||||
}
|
||||
@@ -180,6 +181,7 @@ collapse_value_1: {
|
||||
collapse_value_2: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
keep_fargs: false,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
@@ -188,7 +190,7 @@ collapse_value_2: {
|
||||
})().log("PASS");
|
||||
}
|
||||
expect: {
|
||||
(function(a) {
|
||||
(function() {
|
||||
return console;
|
||||
})().log("PASS");
|
||||
}
|
||||
@@ -554,8 +556,9 @@ drop_fargs: {
|
||||
"bar",
|
||||
]
|
||||
expect_warnings: [
|
||||
"WARN: Dropping unused function argument c [test/compress/default-values.js:1,61]",
|
||||
"WARN: Dropping unused default argument c [test/compress/default-values.js:1,61]",
|
||||
"WARN: Side effects in default value of unused variable b [test/compress/default-values.js:1,37]",
|
||||
"WARN: Dropping unused default argument assignment a [test/compress/default-values.js:1,29]",
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
@@ -1596,3 +1599,65 @@ issue_4548: {
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4588_1_unused: {
|
||||
options = {
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a = 42) {}.length);
|
||||
}
|
||||
expect: {
|
||||
console.log(function(a = 0) {}.length);
|
||||
}
|
||||
expect_stdout: "0"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4588_2_unused: {
|
||||
options = {
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a, b = void 0, c, d = "foo") {}.length);
|
||||
}
|
||||
expect: {
|
||||
console.log(function(a, b = 0, c, d) {}.length);
|
||||
}
|
||||
expect_stdout: "1"
|
||||
expect_warnings: [
|
||||
"WARN: Dropping unused default argument assignment d [test/compress/default-values.js:1,47]",
|
||||
"WARN: Dropping unused default argument value b [test/compress/default-values.js:1,32]",
|
||||
]
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4588_1_evaluate: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a = 42) {}.length);
|
||||
}
|
||||
expect: {
|
||||
console.log(0);
|
||||
}
|
||||
expect_stdout: "0"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4588_2_evaluate: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
unsafe: true,
|
||||
}
|
||||
input: {
|
||||
console.log(function(a, b = void 0, c, d = "foo") {}.length);
|
||||
}
|
||||
expect: {
|
||||
console.log(1);
|
||||
}
|
||||
expect_stdout: "1"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
@@ -2487,3 +2487,30 @@ issue_4554: {
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4584: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
}
|
||||
input: {
|
||||
try {
|
||||
(function f({
|
||||
[console.log(a = "FAIL")]: a,
|
||||
}) {})(0);
|
||||
} catch (e) {
|
||||
console.log("PASS");
|
||||
}
|
||||
}
|
||||
expect: {
|
||||
try {
|
||||
(function f({
|
||||
[console.log(a = "FAIL")]: a,
|
||||
}) {})(0);
|
||||
} catch (e) {
|
||||
console.log("PASS");
|
||||
}
|
||||
}
|
||||
expect_stdout: "PASS"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
@@ -2138,6 +2138,7 @@ issue_3497: {
|
||||
issue_3515_1: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
@@ -2189,6 +2190,7 @@ issue_3515_2: {
|
||||
issue_3515_3: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
@@ -2256,6 +2258,7 @@ function_assign: {
|
||||
issue_3598: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
reduce_vars: true,
|
||||
unused: true,
|
||||
}
|
||||
|
||||
58
test/compress/exponentiation.js
Normal file
58
test/compress/exponentiation.js
Normal file
@@ -0,0 +1,58 @@
|
||||
precedence_1: {
|
||||
input: {
|
||||
console.log(-4 ** 3 ** 2);
|
||||
}
|
||||
expect_exact: "console.log((-4)**3**2);"
|
||||
expect_stdout: "-262144"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
precedence_2: {
|
||||
input: {
|
||||
console.log(-4 ** (3 ** 2));
|
||||
}
|
||||
expect_exact: "console.log((-4)**3**2);"
|
||||
expect_stdout: "-262144"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
precedence_3: {
|
||||
input: {
|
||||
console.log(-(4 ** 3) ** 2);
|
||||
}
|
||||
expect_exact: "console.log((-(4**3))**2);"
|
||||
expect_stdout: "4096"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
precedence_4: {
|
||||
input: {
|
||||
console.log((-4 ** 3) ** 2);
|
||||
}
|
||||
expect_exact: "console.log(((-4)**3)**2);"
|
||||
expect_stdout: "4096"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
await: {
|
||||
input: {
|
||||
(async a => a * await a ** ++a % a)(2).then(console.log);
|
||||
}
|
||||
expect_exact: "(async a=>a*(await a)**++a%a)(2).then(console.log);"
|
||||
expect_stdout: "1"
|
||||
node_version: ">=8"
|
||||
}
|
||||
|
||||
evaluate: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
}
|
||||
input: {
|
||||
console.log(1 + 2 ** 3 - 4);
|
||||
}
|
||||
expect: {
|
||||
console.log(5);
|
||||
}
|
||||
expect_stdout: "5"
|
||||
node_version: ">=8"
|
||||
}
|
||||
@@ -17,7 +17,7 @@ issue_269_1: {
|
||||
expect: {
|
||||
var x = {};
|
||||
console.log(
|
||||
x + "", +x, !!x,
|
||||
"" + x, +("" + x), !!x,
|
||||
"", 0, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ evaluate_3: {
|
||||
console.log(1 + Number(x) + 2);
|
||||
}
|
||||
expect: {
|
||||
console.log(+x + 3);
|
||||
console.log(+("" + x) + 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -979,6 +979,7 @@ collapse_vars_2_strict: {
|
||||
collapse_rhs_true: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
pure_getters: true,
|
||||
}
|
||||
input: {
|
||||
@@ -1015,6 +1016,7 @@ collapse_rhs_true: {
|
||||
collapse_rhs_false: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
pure_getters: false,
|
||||
}
|
||||
input: {
|
||||
@@ -1051,6 +1053,7 @@ collapse_rhs_false: {
|
||||
collapse_rhs_strict: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
pure_getters: "strict",
|
||||
}
|
||||
input: {
|
||||
@@ -1087,6 +1090,7 @@ collapse_rhs_strict: {
|
||||
collapse_rhs_setter: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
evaluate: true,
|
||||
pure_getters: "strict",
|
||||
}
|
||||
input: {
|
||||
|
||||
@@ -663,3 +663,36 @@ issue_4562: {
|
||||
expect_stdout: "f"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
issue_4575: {
|
||||
options = {
|
||||
collapse_vars: true,
|
||||
ie8: true,
|
||||
reduce_vars: true,
|
||||
rests: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
A = "PASS";
|
||||
(function() {
|
||||
var a = 0, b = a;
|
||||
var c = function a(...b) {
|
||||
A;
|
||||
var d = A;
|
||||
console.log(d, b.length);
|
||||
}();
|
||||
})();
|
||||
}
|
||||
expect: {
|
||||
A = "PASS";
|
||||
(function() {
|
||||
(function(b) {
|
||||
A;
|
||||
var d = A;
|
||||
console.log(d, b.length);
|
||||
})([]);
|
||||
})();
|
||||
}
|
||||
expect_stdout: "PASS 0"
|
||||
node_version: ">=6"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,23 @@ console_log: {
|
||||
]
|
||||
}
|
||||
|
||||
console_log_console: {
|
||||
input: {
|
||||
var log = console.log;
|
||||
log(console);
|
||||
log(typeof console.log);
|
||||
}
|
||||
expect: {
|
||||
var log = console.log;
|
||||
log(console);
|
||||
log(typeof console.log);
|
||||
}
|
||||
expect_stdout: [
|
||||
"{ log: 'function(){}' }",
|
||||
"function",
|
||||
]
|
||||
}
|
||||
|
||||
typeof_arguments: {
|
||||
options = {
|
||||
evaluate: true,
|
||||
@@ -81,6 +98,63 @@ log_global: {
|
||||
expect_stdout: "[object global]"
|
||||
}
|
||||
|
||||
log_nested: {
|
||||
options = {
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var o = { p: 42 };
|
||||
for (var i = 0; i < 10; i++)
|
||||
o = {
|
||||
p: o,
|
||||
q: function foo() {},
|
||||
};
|
||||
console.log(o);
|
||||
}
|
||||
expect: {
|
||||
var o = { p: 42 };
|
||||
for (var i = 0; i < 10; i++)
|
||||
o = {
|
||||
p: o,
|
||||
q: function() {},
|
||||
};
|
||||
console.log(o);
|
||||
}
|
||||
expect_stdout: true
|
||||
}
|
||||
|
||||
timers: {
|
||||
options = {
|
||||
reduce_vars: true,
|
||||
toplevel: true,
|
||||
unused: true,
|
||||
}
|
||||
input: {
|
||||
var count = 0, interval = 1000, duration = 3210;
|
||||
var timer = setInterval(function() {
|
||||
console.log(++count);
|
||||
}, interval);
|
||||
setTimeout(function() {
|
||||
clearInterval(timer);
|
||||
}, duration);
|
||||
}
|
||||
expect: {
|
||||
var count = 0;
|
||||
var timer = setInterval(function() {
|
||||
console.log(++count);
|
||||
}, 1000);
|
||||
setTimeout(function() {
|
||||
clearInterval(timer);
|
||||
}, 3210);
|
||||
}
|
||||
expect_stdout: [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
]
|
||||
node_version: ">=0.12"
|
||||
}
|
||||
|
||||
issue_4054: {
|
||||
input: {
|
||||
console.log({
|
||||
|
||||
@@ -56,7 +56,7 @@ describe("bin/uglifyjs", function() {
|
||||
"--source-map", [
|
||||
"names=true",
|
||||
"url=inline",
|
||||
].join(","),
|
||||
].join(),
|
||||
].join(" "), function(err, stdout) {
|
||||
if (err) throw err;
|
||||
var expected = [
|
||||
@@ -84,7 +84,7 @@ describe("bin/uglifyjs", function() {
|
||||
"--source-map", [
|
||||
"names=false",
|
||||
"url=inline",
|
||||
].join(","),
|
||||
].join(),
|
||||
].join(" "), function(err, stdout) {
|
||||
if (err) throw err;
|
||||
var expected = [
|
||||
@@ -171,7 +171,7 @@ describe("bin/uglifyjs", function() {
|
||||
"content=" + mapFile,
|
||||
"includeSources",
|
||||
"url=inline",
|
||||
].join(","),
|
||||
].join(),
|
||||
].join(" ");
|
||||
|
||||
var child = exec(command, function(err, stdout) {
|
||||
|
||||
@@ -70,8 +70,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
||||
} else if (differs.error) {
|
||||
differs.warnings = warnings;
|
||||
return differs;
|
||||
} else if (is_error(differs.unminified_result)
|
||||
&& is_error(differs.minified_result)
|
||||
} else if (sandbox.is_error(differs.unminified_result)
|
||||
&& sandbox.is_error(differs.minified_result)
|
||||
&& differs.unminified_result.name == differs.minified_result.name) {
|
||||
return {
|
||||
code: [
|
||||
@@ -558,8 +558,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
||||
log(code);
|
||||
log(diff.error.stack);
|
||||
log("*** Discarding permutation and continuing.");
|
||||
} else if (is_error(diff.unminified_result)
|
||||
&& is_error(diff.minified_result)
|
||||
} else if (sandbox.is_error(diff.unminified_result)
|
||||
&& sandbox.is_error(diff.minified_result)
|
||||
&& diff.unminified_result.name == diff.minified_result.name) {
|
||||
// ignore difference in error messages caused by minification
|
||||
diff_error_message = testcase;
|
||||
@@ -600,10 +600,10 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
||||
}
|
||||
var lines = [ "" ];
|
||||
if (isNaN(max_timeout)) {
|
||||
lines.push("// minify error: " + to_comment(strip_color_codes(differs.minified_result.stack)));
|
||||
lines.push("// minify error: " + to_comment(differs.minified_result.stack));
|
||||
} else {
|
||||
var unminified_result = strip_color_codes(differs.unminified_result);
|
||||
var minified_result = strip_color_codes(differs.minified_result);
|
||||
var unminified_result = differs.unminified_result;
|
||||
var minified_result = differs.minified_result;
|
||||
if (trim_trailing_whitespace(unminified_result) == trim_trailing_whitespace(minified_result)) {
|
||||
lines.push(
|
||||
"// (stringified)",
|
||||
@@ -624,10 +624,6 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
|
||||
}
|
||||
};
|
||||
|
||||
function strip_color_codes(value) {
|
||||
return ("" + value).replace(/\u001b\[\d+m/g, "");
|
||||
}
|
||||
|
||||
function to_comment(value) {
|
||||
return ("" + value).replace(/\n/g, "\n// ");
|
||||
}
|
||||
@@ -665,12 +661,8 @@ function has_loopcontrol(body, loop, label) {
|
||||
return found;
|
||||
}
|
||||
|
||||
function is_error(result) {
|
||||
return result && typeof result.name == "string" && typeof result.message == "string";
|
||||
}
|
||||
|
||||
function is_timed_out(result) {
|
||||
return is_error(result) && /timed out/.test(result.message);
|
||||
return sandbox.is_error(result) && /timed out/.test(result.message);
|
||||
}
|
||||
|
||||
function is_statement(node) {
|
||||
|
||||
344
test/sandbox.js
344
test/sandbox.js
@@ -1,103 +1,39 @@
|
||||
var readFileSync = require("fs").readFileSync;
|
||||
var semver = require("semver");
|
||||
var spawnSync = require("child_process").spawnSync;
|
||||
var vm = require("vm");
|
||||
|
||||
var setupContext = new vm.Script([
|
||||
"[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {",
|
||||
" f.toString = Function.prototype.toString;",
|
||||
"});",
|
||||
"Function.prototype.toString = function() {",
|
||||
" var id = 100000;",
|
||||
" return function() {",
|
||||
" 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 n;",
|
||||
" }",
|
||||
" });",
|
||||
] : [], [
|
||||
" }",
|
||||
' return "function(){}";',
|
||||
" };",
|
||||
"}();",
|
||||
"this;",
|
||||
]).join("\n"));
|
||||
|
||||
function createContext() {
|
||||
var ctx = vm.createContext(Object.defineProperties({}, {
|
||||
console: { value: { log: log } },
|
||||
global: { get: self },
|
||||
self: { get: self },
|
||||
window: { get: self },
|
||||
}));
|
||||
var global = setupContext.runInContext(ctx);
|
||||
return ctx;
|
||||
|
||||
function self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
function safe_log(arg, level) {
|
||||
if (arg) switch (typeof arg) {
|
||||
case "function":
|
||||
return arg.toString();
|
||||
case "object":
|
||||
if (arg === global) return "[object global]";
|
||||
if (/Error$/.test(arg.name)) return arg.toString();
|
||||
if (typeof arg.then == "function") return "[object Promise]";
|
||||
arg.constructor.toString();
|
||||
if (level--) for (var key in arg) {
|
||||
var desc = Object.getOwnPropertyDescriptor(arg, key);
|
||||
if (!desc || !desc.get && !desc.set) arg[key] = safe_log(arg[key], level);
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
if (arguments.length == 1 && typeof msg == "string") return console.log("%s", msg);
|
||||
return console.log.apply(console, [].map.call(arguments, function(arg) {
|
||||
return safe_log(arg, 3);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function run_code(code, toplevel, timeout) {
|
||||
timeout = timeout || 5000;
|
||||
var stdout = "";
|
||||
var original_write = process.stdout.write;
|
||||
process.stdout.write = function(chunk) {
|
||||
stdout += chunk;
|
||||
};
|
||||
try {
|
||||
vm.runInContext(toplevel ? "(function(){" + code + "})()" : code, createContext(), { timeout: timeout });
|
||||
return stdout;
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
} finally {
|
||||
process.stdout.write = original_write;
|
||||
}
|
||||
}
|
||||
|
||||
setup_log();
|
||||
var setup_code = "(" + setup + ")(" + [
|
||||
"this",
|
||||
find_builtins(),
|
||||
setup_log,
|
||||
"function(process) {" + readFileSync(require.resolve("../tools/tty", "utf8")) + "}",
|
||||
].join(",\n") + ");\n";
|
||||
exports.has_toplevel = function(options) {
|
||||
return options.toplevel
|
||||
|| options.mangle && options.mangle.toplevel
|
||||
|| options.compress && options.compress.toplevel;
|
||||
};
|
||||
exports.is_error = is_error;
|
||||
exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, toplevel, timeout) {
|
||||
var stdout = run_code(code, toplevel, timeout);
|
||||
var stdout = run_code_vm(code, toplevel, timeout);
|
||||
if (typeof stdout != "string" || !/arguments/.test(code)) return stdout;
|
||||
do {
|
||||
var prev = stdout;
|
||||
stdout = run_code(code, toplevel, timeout);
|
||||
stdout = run_code_vm(code, toplevel, timeout);
|
||||
} while (prev !== stdout);
|
||||
return stdout;
|
||||
} : run_code;
|
||||
|
||||
function strip_func_ids(text) {
|
||||
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
|
||||
}
|
||||
|
||||
} : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) {
|
||||
if (/\basync([ \t]+[^\s()[\]{},.&|!~=*%/+-]+|[ \t]*\([\s\S]*?\))[ \t]*=>|\b(async[ \t]+function|setInterval|setTimeout)\b/.test(code)) {
|
||||
return run_code_exec(code, toplevel, timeout);
|
||||
} else {
|
||||
return run_code_vm(code, toplevel, timeout);
|
||||
}
|
||||
};
|
||||
exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expected, actual) {
|
||||
if (typeof expected != typeof actual) return false;
|
||||
if (typeof expected == "object" && typeof expected.name == "string" && typeof expected.message == "string") {
|
||||
if (is_error(expected)) {
|
||||
if (expected.name !== actual.name) return false;
|
||||
if (typeof actual.message != "string") return false;
|
||||
expected = expected.message.slice(expected.message.lastIndexOf("\n") + 1);
|
||||
@@ -107,8 +43,228 @@ exports.same_stdout = semver.satisfies(process.version, "0.12") ? function(expec
|
||||
} : function(expected, actual) {
|
||||
return typeof expected == typeof actual && strip_func_ids(expected) == strip_func_ids(actual);
|
||||
};
|
||||
exports.has_toplevel = function(options) {
|
||||
return options.toplevel
|
||||
|| options.mangle && options.mangle.toplevel
|
||||
|| options.compress && options.compress.toplevel;
|
||||
};
|
||||
|
||||
function is_error(result) {
|
||||
return result && typeof result.name == "string" && typeof result.message == "string";
|
||||
}
|
||||
|
||||
function strip_color_codes(value) {
|
||||
return value.replace(/\u001b\[\d+m/g, "");
|
||||
}
|
||||
|
||||
function strip_func_ids(text) {
|
||||
return ("" + text).replace(/F[0-9]{6}N/g, "<F<>N>");
|
||||
}
|
||||
|
||||
function setup_log() {
|
||||
var inspect = require("util").inspect;
|
||||
if (inspect.defaultOptions) {
|
||||
var log_options = {
|
||||
breakLength: Infinity,
|
||||
colors: false,
|
||||
compact: true,
|
||||
customInspect: false,
|
||||
depth: Infinity,
|
||||
maxArrayLength: Infinity,
|
||||
maxStringLength: Infinity,
|
||||
showHidden: false,
|
||||
};
|
||||
for (var name in log_options) {
|
||||
if (name in inspect.defaultOptions) inspect.defaultOptions[name] = log_options[name];
|
||||
}
|
||||
}
|
||||
return inspect;
|
||||
}
|
||||
|
||||
function find_builtins() {
|
||||
setup_code = "console.log(Object.keys(this));";
|
||||
var builtins = run_code_vm("");
|
||||
if (semver.satisfies(process.version, ">=0.12")) builtins += ".concat(" + run_code_exec("") + ")";
|
||||
return builtins;
|
||||
}
|
||||
|
||||
function setup(global, builtins, setup_log, setup_tty) {
|
||||
[ Array, Boolean, Error, Function, Number, Object, RegExp, String ].forEach(function(f) {
|
||||
f.toString = Function.prototype.toString;
|
||||
});
|
||||
Function.prototype.toString = function() {
|
||||
var configurable = Object.getOwnPropertyDescriptor(Function.prototype, "name").configurable;
|
||||
var id = 100000;
|
||||
return function() {
|
||||
var n = this.name;
|
||||
if (!/^F[0-9]{6}N$/.test(n)) {
|
||||
n = "F" + ++id + "N";
|
||||
if (configurable) Object.defineProperty(this, "name", {
|
||||
get: function() {
|
||||
return n;
|
||||
}
|
||||
});
|
||||
}
|
||||
return "function(){}";
|
||||
};
|
||||
}();
|
||||
var process = global.process;
|
||||
if (process) {
|
||||
setup_tty(process);
|
||||
var inspect = setup_log();
|
||||
process.on("uncaughtException", function(ex) {
|
||||
var value = ex;
|
||||
if (value instanceof Error) {
|
||||
value = {};
|
||||
for (var name in ex) {
|
||||
value[name] = ex[name];
|
||||
delete ex[name];
|
||||
}
|
||||
}
|
||||
process.stderr.write(inspect(value) + "\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
|
||||
throw ex;
|
||||
}).on("unhandledRejection", function() {});
|
||||
}
|
||||
var log = console.log;
|
||||
var safe_console = {
|
||||
log: function(msg) {
|
||||
if (arguments.length == 1 && typeof msg == "string") return log("%s", msg);
|
||||
return log.apply(null, [].map.call(arguments, function(arg) {
|
||||
return safe_log(arg, {
|
||||
level: 5,
|
||||
original: [],
|
||||
replaced: [],
|
||||
});
|
||||
}));
|
||||
},
|
||||
};
|
||||
var props = {
|
||||
// for Node.js v8
|
||||
console: {
|
||||
get: function() {
|
||||
return safe_console;
|
||||
},
|
||||
},
|
||||
global: { get: self },
|
||||
self: { get: self },
|
||||
window: { get: self },
|
||||
};
|
||||
[
|
||||
// for Node.js v0.12
|
||||
"Buffer",
|
||||
"clearInterval",
|
||||
"clearTimeout",
|
||||
// for Node.js v0.12
|
||||
"DTRACE_NET_STREAM_END",
|
||||
// for Node.js v8
|
||||
"process",
|
||||
"setInterval",
|
||||
"setTimeout",
|
||||
].forEach(function(name) {
|
||||
var value = global[name];
|
||||
props[name] = {
|
||||
get: function() {
|
||||
return value;
|
||||
},
|
||||
};
|
||||
});
|
||||
builtins.forEach(function(name) {
|
||||
try {
|
||||
delete global[name];
|
||||
} catch (e) {}
|
||||
});
|
||||
Object.defineProperties(global, props);
|
||||
// for Node.js v8+
|
||||
global.toString = function() {
|
||||
return "[object global]";
|
||||
};
|
||||
|
||||
function self() {
|
||||
return this;
|
||||
}
|
||||
|
||||
function safe_log(arg, cache) {
|
||||
if (arg) switch (typeof arg) {
|
||||
case "function":
|
||||
return arg.toString();
|
||||
case "object":
|
||||
if (arg === global) return "[object global]";
|
||||
if (/Error$/.test(arg.name)) return arg.toString();
|
||||
if (typeof arg.then == "function") return "[object Promise]";
|
||||
arg.constructor.toString();
|
||||
var index = cache.original.indexOf(arg);
|
||||
if (index >= 0) return cache.replaced[index];
|
||||
if (--cache.level < 0) return "[object Object]";
|
||||
var value = {};
|
||||
cache.original.push(arg);
|
||||
cache.replaced.push(value);
|
||||
for (var key in arg) {
|
||||
var desc = Object.getOwnPropertyDescriptor(arg, key);
|
||||
if (desc && (desc.get || desc.set)) {
|
||||
Object.defineProperty(value, key, desc);
|
||||
} else {
|
||||
value[key] = safe_log(arg[key], cache);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
function run_code_vm(code, toplevel, timeout) {
|
||||
timeout = timeout || 5000;
|
||||
var stdout = "";
|
||||
var original_write = process.stdout.write;
|
||||
process.stdout.write = function(chunk) {
|
||||
stdout += chunk;
|
||||
};
|
||||
try {
|
||||
var ctx = vm.createContext({ console: console });
|
||||
// for Node.js v6
|
||||
vm.runInContext(setup_code, ctx);
|
||||
vm.runInContext(toplevel ? "(function(){" + code + "})();" : code, ctx, { timeout: timeout });
|
||||
return strip_color_codes(stdout);
|
||||
} catch (ex) {
|
||||
return ex;
|
||||
} finally {
|
||||
process.stdout.write = original_write;
|
||||
}
|
||||
}
|
||||
|
||||
function run_code_exec(code, toplevel, timeout) {
|
||||
if (toplevel) {
|
||||
code = setup_code + "(function(){" + code + "})();";
|
||||
} else {
|
||||
code = code.replace(/^((["'])[^"']*\2(;|$))?/, function(directive) {
|
||||
return directive + setup_code;
|
||||
});
|
||||
}
|
||||
var result = spawnSync(process.argv[0], [ '--max-old-space-size=2048' ], {
|
||||
encoding: "utf8",
|
||||
input: code,
|
||||
stdio: "pipe",
|
||||
timeout: timeout || 5000,
|
||||
});
|
||||
if (result.status === 0) return result.stdout;
|
||||
if (result.error && result.error.code == "ETIMEDOUT" || /FATAL ERROR:/.test(msg)) {
|
||||
return new Error("Script execution timed out.");
|
||||
}
|
||||
if (result.error) return result.error;
|
||||
var msg = result.stderr.replace(/\r\n/g, "\n");
|
||||
var end = msg.indexOf("\n\n-----===== UNCAUGHT EXCEPTION =====-----\n\n");
|
||||
var details;
|
||||
if (end >= 0) {
|
||||
details = msg.slice(0, end).replace(/<([1-9][0-9]*) empty items?>/g, function(match, count) {
|
||||
return new Array(+count).join();
|
||||
});
|
||||
try {
|
||||
details = vm.runInNewContext("(" + details + ")");
|
||||
} catch (e) {}
|
||||
}
|
||||
var match = /\n([^:\s]*Error)(?:: ([\s\S]+?))?\n( at [\s\S]+)\n$/.exec(msg);
|
||||
if (!match) return details;
|
||||
var ex = new global[match[1]](match[2]);
|
||||
ex.stack = ex.stack.slice(0, ex.stack.indexOf(" at ")) + match[3];
|
||||
if (typeof details == "object") {
|
||||
for (var name in details) ex[name] = details[name];
|
||||
} else if (end >= 0) {
|
||||
ex.details = details;
|
||||
}
|
||||
return ex;
|
||||
}
|
||||
|
||||
@@ -10,16 +10,17 @@ exports.init = function(url, auth, num) {
|
||||
exports.should_stop = function(callback) {
|
||||
read(base + "/actions/runs?per_page=100", function(reply) {
|
||||
if (!reply || !Array.isArray(reply.workflow_runs)) return;
|
||||
var runs = reply.workflow_runs.filter(function(workflow) {
|
||||
return workflow.status != "completed";
|
||||
}).sort(function(a, b) {
|
||||
var runs = reply.workflow_runs.sort(function(a, b) {
|
||||
return b.run_number - a.run_number;
|
||||
});
|
||||
var found = false, remaining = 20;
|
||||
(function next() {
|
||||
if (!runs.length) return;
|
||||
var workflow = runs.pop();
|
||||
if (workflow.event == "schedule" && workflow.run_number == run_number) found = true;
|
||||
var workflow;
|
||||
do {
|
||||
workflow = runs.pop();
|
||||
if (!workflow) return;
|
||||
if (workflow.event == "schedule" && workflow.run_number == run_number) found = true;
|
||||
} while (!found && workflow.status == "completed");
|
||||
read(workflow.jobs_url, function(reply) {
|
||||
if (!reply || !Array.isArray(reply.jobs)) return;
|
||||
if (!reply.jobs.every(function(job) {
|
||||
|
||||
@@ -134,11 +134,13 @@ var SUPPORT = function(matrix) {
|
||||
}({
|
||||
arrow: "a => 0;",
|
||||
async: "async function f(){}",
|
||||
bigint: "42n",
|
||||
catch_omit_var: "try {} catch {}",
|
||||
computed_key: "({[0]: 0});",
|
||||
const_block: "var a; { const a = 0; }",
|
||||
default_value: "[ a = 0 ] = [];",
|
||||
destructuring: "[] = [];",
|
||||
exponentiation: "0 ** 0",
|
||||
let: "let a;",
|
||||
rest: "var [...a] = [];",
|
||||
rest_object: "var {...a} = {};",
|
||||
@@ -167,7 +169,7 @@ var VALUES = [
|
||||
"4",
|
||||
"5",
|
||||
"22",
|
||||
"-0", // 0/-0 !== 0
|
||||
"(-0)", // 0/-0 !== 0
|
||||
"23..toString()",
|
||||
"24 .toString()",
|
||||
"25. ",
|
||||
@@ -188,6 +190,12 @@ var VALUES = [
|
||||
'"function"',
|
||||
"this",
|
||||
];
|
||||
if (SUPPORT.bigint) VALUES = VALUES.concat([
|
||||
"(!0o644n)",
|
||||
"([3n][0] > 2)",
|
||||
"(-42n).toString()",
|
||||
"Number(0XDEADn << 16n | 0xbeefn)",
|
||||
]);
|
||||
|
||||
var BINARY_OPS = [
|
||||
" + ", // spaces needed to disambiguate with ++ cases (could otherwise cause syntax errors)
|
||||
@@ -217,6 +225,7 @@ var BINARY_OPS = [
|
||||
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
|
||||
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
|
||||
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
|
||||
if (SUPPORT.exponentiation) BINARY_OPS.push("**");
|
||||
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
|
||||
BINARY_OPS = BINARY_OPS.concat(BINARY_OPS);
|
||||
BINARY_OPS.push(" in ");
|
||||
@@ -317,8 +326,6 @@ var VAR_NAMES = [
|
||||
"NaN",
|
||||
"Infinity",
|
||||
"arguments",
|
||||
"Math",
|
||||
"parseInt",
|
||||
"async",
|
||||
"await",
|
||||
];
|
||||
@@ -433,6 +440,22 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
|
||||
unique_vars.length = len;
|
||||
return pairs;
|
||||
|
||||
function mapShuffled(values, fn) {
|
||||
var declare_only = [];
|
||||
var side_effects = [];
|
||||
values.forEach(function(value, index) {
|
||||
value = fn(value, index);
|
||||
if (/]:|=/.test(value) ? canThrow && rng(10) == 0 : rng(5)) {
|
||||
declare_only.splice(rng(declare_only.length + 1), 0, value);
|
||||
} else if (canThrow && rng(5) == 0) {
|
||||
side_effects.splice(rng(side_effects.length + 1), 0, value);
|
||||
} else {
|
||||
side_effects.push(value);
|
||||
}
|
||||
});
|
||||
return declare_only.concat(side_effects);
|
||||
}
|
||||
|
||||
function convertToRest(names) {
|
||||
var last = names.length - 1;
|
||||
if (last >= 0 && SUPPORT.rest && rng(20) == 0) {
|
||||
@@ -563,19 +586,24 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
|
||||
}
|
||||
});
|
||||
fill(function() {
|
||||
var last = pairs.names.length - 1, has_rest = false;
|
||||
var s = pairs.names.map(function(name, index) {
|
||||
var last = pairs.names.length - 1, rest;
|
||||
if (last >= 0 && !(last in keys) && SUPPORT.rest_object && rng(20) == 0) {
|
||||
rest = pairs.names.pop();
|
||||
if (!/=/.test(rest)) rest = "..." + rest;
|
||||
}
|
||||
var s = mapShuffled(pairs.names, function(name, index) {
|
||||
if (index in keys) return keys[index] + ": " + name;
|
||||
if (index == last && SUPPORT.rest_object && rng(20) == 0 && name.indexOf("=") < 0) {
|
||||
has_rest = true;
|
||||
return "..." + name;
|
||||
}
|
||||
return rng(10) == 0 ? name : createKey(recurmax, keys) + ": " + name;
|
||||
}).join(", ");
|
||||
if (!has_rest) s = addTrailingComma(s);
|
||||
});
|
||||
if (rest) {
|
||||
s.push(rest);
|
||||
s = s.join(", ");
|
||||
} else {
|
||||
s = addTrailingComma(s.join(", "));
|
||||
}
|
||||
names.unshift("{ " + s + " }" + createDefaultValue(recurmax, noDefault));
|
||||
}, function() {
|
||||
values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
|
||||
values.unshift("{ " + addTrailingComma(mapShuffled(pairs.values, function(value, index) {
|
||||
var key = index in keys ? keys[index] : createKey(recurmax, keys);
|
||||
return key + ": " + value;
|
||||
}).join(", ")) + " }");
|
||||
@@ -1020,9 +1048,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
|
||||
case 0:
|
||||
return [
|
||||
"[ ",
|
||||
new Array(rng(3)).join(","),
|
||||
new Array(rng(3)).join(),
|
||||
getVarName(NO_CONST),
|
||||
new Array(rng(3)).join(","),
|
||||
new Array(rng(3)).join(),
|
||||
" ] = ",
|
||||
createArrayLiteral(recurmax, stmtDepth, canThrow),
|
||||
].join("");
|
||||
@@ -1312,10 +1340,11 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
|
||||
var save_async = async;
|
||||
var s;
|
||||
var name = createObjectKey(recurmax, stmtDepth, canThrow);
|
||||
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
|
||||
switch (rng(SUPPORT.computed_key ? 3 : 2)) {
|
||||
case 0:
|
||||
async = false;
|
||||
var fn;
|
||||
switch (rng(SUPPORT.computed_key ? 3 : 2)) {
|
||||
case 0:
|
||||
async = false;
|
||||
fn = function(defns) {
|
||||
s = [
|
||||
"get " + name + "(){",
|
||||
strictMode(),
|
||||
@@ -1324,13 +1353,15 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
|
||||
createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
|
||||
"},",
|
||||
];
|
||||
break;
|
||||
case 1:
|
||||
var prop;
|
||||
do {
|
||||
prop = getDotKey();
|
||||
} while (name == prop);
|
||||
async = false;
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
var prop;
|
||||
do {
|
||||
prop = getDotKey();
|
||||
} while (name == prop);
|
||||
async = false;
|
||||
fn = function(defns) {
|
||||
s = [
|
||||
"set " + name + "(" + createVarName(MANDATORY) + "){",
|
||||
strictMode(),
|
||||
@@ -1339,9 +1370,11 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
|
||||
"this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";",
|
||||
"},",
|
||||
];
|
||||
break;
|
||||
default:
|
||||
async = SUPPORT.async && rng(50) == 0;
|
||||
};
|
||||
break;
|
||||
default:
|
||||
async = SUPPORT.async && rng(50) == 0;
|
||||
fn = function(defns) {
|
||||
s = [
|
||||
(async ? "async " : "") + name + "(" + createParams(save_async, NO_DUPLICATE) + "){",
|
||||
strictMode(),
|
||||
@@ -1349,9 +1382,10 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
|
||||
_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
|
||||
"},",
|
||||
]
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
break;
|
||||
}
|
||||
createBlockVariables(recurmax, stmtDepth, canThrow, fn);
|
||||
async = save_async;
|
||||
VAR_NAMES.length = nameLenBefore;
|
||||
return filterDirective(s).join("\n");
|
||||
@@ -1557,9 +1591,10 @@ function getVarName(noConst) {
|
||||
function createVarName(maybe, dontStore) {
|
||||
if (!maybe || rng(2)) {
|
||||
var suffix = rng(3);
|
||||
var name;
|
||||
var name, tries = 10;
|
||||
do {
|
||||
name = VAR_NAMES[rng(VAR_NAMES.length)];
|
||||
if (--tries < 0) suffix++;
|
||||
if (suffix) name += "_" + suffix;
|
||||
} while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 || async && name == "await");
|
||||
if (!dontStore) VAR_NAMES.push(name);
|
||||
@@ -1743,24 +1778,52 @@ function log(options) {
|
||||
}
|
||||
|
||||
function sort_globals(code) {
|
||||
var globals = sandbox.run_code("throw Object.keys(this).sort();" + code);
|
||||
var globals = sandbox.run_code("throw Object.keys(this).sort(" + function(global) {
|
||||
return function(m, n) {
|
||||
return (n == "toString") - (m == "toString")
|
||||
|| (typeof global[n] == "function") - (typeof global[m] == "function")
|
||||
|| (m < n ? -1 : m > n ? 1 : 0);
|
||||
};
|
||||
} + "(this));" + code);
|
||||
if (!Array.isArray(globals)) {
|
||||
errorln();
|
||||
errorln();
|
||||
errorln("//-------------------------------------------------------------");
|
||||
errorln("// !!! sort_globals() failed !!!");
|
||||
errorln("// expected Array, got:");
|
||||
if (!sandbox.is_error(globals)) try {
|
||||
globals = JSON.stringify(globals);
|
||||
} catch (e) {}
|
||||
errorln(globals);
|
||||
errorln("//");
|
||||
errorln(code);
|
||||
errorln();
|
||||
return code;
|
||||
}
|
||||
return globals.length ? "var " + globals.map(function(name) {
|
||||
return name + "=" + name;
|
||||
}).join(",") + ";" + code : code;
|
||||
}).join() + ";" + code : code;
|
||||
}
|
||||
|
||||
function fuzzy_match(original, uglified) {
|
||||
uglified = uglified.split(" ");
|
||||
var i = uglified.length;
|
||||
original = original.split(" ", i);
|
||||
while (--i >= 0) {
|
||||
if (original[i] === uglified[i]) continue;
|
||||
var a = +original[i];
|
||||
var b = +uglified[i];
|
||||
if (Math.abs((b - a) / a) < 1e-10) continue;
|
||||
return false;
|
||||
var m = [], n = [];
|
||||
if (collect(original, m) !== collect(uglified, n)) return false;
|
||||
for (var i = 0; i < m.length; i++) {
|
||||
var a = m[i];
|
||||
var b = n[i];
|
||||
if (Math.abs((b - a) / a) > 1e-10) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
function collect(input, nums) {
|
||||
return input.replace(/-?([1-9][0-9]*(\.[0-9]+)?|0\.[0-9]+)(e-?[1-9][0-9]*)?/ig, function(num) {
|
||||
return "<|" + nums.push(+num) + "|>";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function is_error_timeout(ex) {
|
||||
return /timed out/.test(ex.message);
|
||||
}
|
||||
|
||||
function is_error_in(ex) {
|
||||
@@ -1768,13 +1831,17 @@ function is_error_in(ex) {
|
||||
}
|
||||
|
||||
function is_error_spread(ex) {
|
||||
return ex.name == "TypeError" && /Found non-callable @@iterator|is not iterable|undefined is not a function/.test(ex.message);
|
||||
return ex.name == "TypeError" && /Found non-callable @@iterator| is not iterable| is not a function/.test(ex.message);
|
||||
}
|
||||
|
||||
function is_error_recursion(ex) {
|
||||
return ex.name == "RangeError" && /Invalid string length|Maximum call stack size exceeded/.test(ex.message);
|
||||
}
|
||||
|
||||
function is_error_destructuring(ex) {
|
||||
return ex.name == "TypeError" && /^Cannot destructure /.test(ex.message);
|
||||
}
|
||||
|
||||
function patch_try_catch(orig, toplevel) {
|
||||
var stack = [ {
|
||||
code: orig,
|
||||
@@ -1782,6 +1849,7 @@ function patch_try_catch(orig, toplevel) {
|
||||
offset: 0,
|
||||
tries: [],
|
||||
} ];
|
||||
var tail_throw = '\nif (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;\n';
|
||||
var re = /(?:(?:^|[\s{}):;])try|}\s*catch\s*\(([^)[{]+)\)|}\s*finally)\s*(?={)/g;
|
||||
while (stack.length) {
|
||||
var code = stack[0].code;
|
||||
@@ -1798,7 +1866,7 @@ function patch_try_catch(orig, toplevel) {
|
||||
var insert;
|
||||
if (/}\s*finally\s*$/.test(match[0])) {
|
||||
tries.shift();
|
||||
insert = 'if (typeof UFUZZ_ERROR == "object") throw UFUZZ_ERROR;';
|
||||
insert = tail_throw;
|
||||
} else {
|
||||
while (tries.length && tries[0].catch) tries.shift();
|
||||
tries[0].catch = index - offset;
|
||||
@@ -1812,9 +1880,9 @@ function patch_try_catch(orig, toplevel) {
|
||||
"throw " + match[1] + ";",
|
||||
].join("\n");
|
||||
}
|
||||
var new_code = code.slice(0, index) + insert + code.slice(index);
|
||||
var new_code = code.slice(0, index) + insert + code.slice(index) + tail_throw;
|
||||
var result = sandbox.run_code(new_code, toplevel);
|
||||
if (typeof result != "object" || typeof result.name != "string" || typeof result.message != "string") {
|
||||
if (!sandbox.is_error(result)) {
|
||||
if (!stack.filled && match[1]) stack.push({
|
||||
code: code,
|
||||
index: index && index - 1,
|
||||
@@ -1832,6 +1900,9 @@ function patch_try_catch(orig, toplevel) {
|
||||
} else if (is_error_recursion(result)) {
|
||||
index = result.ufuzz_try;
|
||||
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
|
||||
} else if (is_error_destructuring(result)) {
|
||||
index = result.ufuzz_catch;
|
||||
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index);
|
||||
}
|
||||
}
|
||||
stack.filled = true;
|
||||
@@ -1887,6 +1958,7 @@ for (var round = 1; round <= num_iterations; round++) {
|
||||
println("original result:");
|
||||
println(orig_result[0]);
|
||||
println();
|
||||
// ignore v8 parser bug
|
||||
if (is_bug_async_arrow_rest(orig_result[0])) continue;
|
||||
}
|
||||
minify_options.forEach(function(options) {
|
||||
@@ -1899,13 +1971,19 @@ for (var round = 1; round <= num_iterations; round++) {
|
||||
uglify_code = uglify_code.code;
|
||||
uglify_result = sandbox.run_code(uglify_code, toplevel);
|
||||
ok = sandbox.same_stdout(original_result, uglify_result);
|
||||
// ignore v8 parser bug
|
||||
if (!ok && is_bug_async_arrow_rest(uglify_result)) ok = true;
|
||||
// ignore declaration order of global variables
|
||||
if (!ok && !toplevel) {
|
||||
ok = sandbox.same_stdout(sandbox.run_code(sort_globals(original_code)), sandbox.run_code(sort_globals(uglify_code)));
|
||||
}
|
||||
// ignore numerical imprecision caused by `unsafe_math`
|
||||
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == "string" && typeof uglify_result == "string") {
|
||||
ok = fuzzy_match(original_result, uglify_result);
|
||||
if (!ok && o.compress && o.compress.unsafe_math && typeof original_result == typeof uglify_result) {
|
||||
if (typeof original_result == "string") {
|
||||
ok = fuzzy_match(original_result, uglify_result);
|
||||
} else if (sandbox.is_error(original_result)) {
|
||||
ok = original_result.name == uglify_result.name && fuzzy_match(original_result.message, uglify_result.message);
|
||||
}
|
||||
if (!ok) {
|
||||
var fuzzy_result = sandbox.run_code(original_code.replace(/( - 0\.1){3}/g, " - 0.3"), toplevel);
|
||||
ok = sandbox.same_stdout(fuzzy_result, uglify_result);
|
||||
@@ -1913,10 +1991,15 @@ for (var round = 1; round <= num_iterations; round++) {
|
||||
}
|
||||
// ignore difference in error message caused by Temporal Dead Zone
|
||||
if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true;
|
||||
// ignore spurious time-outs
|
||||
if (!ok && errored && /timed out/.test(original_result.message) && !/timed out/.test(uglify_result.message)) {
|
||||
if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = sandbox.run_code(original_code, toplevel, 10000);
|
||||
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
|
||||
if (!ok && errored && is_error_timeout(original_result)) {
|
||||
if (is_error_timeout(uglify_result)) {
|
||||
// ignore difference in error message
|
||||
ok = true;
|
||||
} else {
|
||||
// ignore spurious time-outs
|
||||
if (!orig_result[toplevel ? 3 : 2]) orig_result[toplevel ? 3 : 2] = sandbox.run_code(original_code, toplevel, 10000);
|
||||
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
|
||||
}
|
||||
}
|
||||
// ignore difference in error message caused by `in`
|
||||
if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true;
|
||||
@@ -1926,6 +2009,10 @@ for (var round = 1; round <= num_iterations; round++) {
|
||||
if (!ok && errored && is_error_recursion(original_result)) {
|
||||
if (is_error_recursion(uglify_result) || typeof uglify_result == "string") ok = true;
|
||||
}
|
||||
// ignore difference in error message caused by destructuring
|
||||
if (!ok && errored && is_error_destructuring(uglify_result) && is_error_destructuring(original_result)) {
|
||||
ok = true;
|
||||
}
|
||||
// ignore errors above when caught by try-catch
|
||||
if (!ok) {
|
||||
var orig_skipped = patch_try_catch(original_code, toplevel);
|
||||
|
||||
Reference in New Issue
Block a user