Compare commits

...

41 Commits

Author SHA1 Message Date
Alex Lam S.L
8b10b93ee1 v3.12.2 2020-12-16 14:11:48 +08:00
Alex Lam S.L
549de028b6 fix corner case in objects (#4381)
fixes #4380
2020-12-15 21:23:55 +08:00
Alex Lam S.L
f579f1aa47 emulate global context in Node.js & web (#4379) 2020-12-14 02:05:07 +08:00
Alex Lam S.L
fcc40d0502 fix corner case in dead_code (#4378)
fixes #4377
2020-12-14 00:03:44 +08:00
Alex Lam S.L
b309527264 maintain compatibility options when testing (#4376) 2020-12-13 14:26:45 +08:00
Alex Lam S.L
5d19bb8d5d fix corner case in booleans (#4375)
fixes #4374
2020-12-13 05:01:38 +08:00
Alex Lam S.L
af97629912 fix corner case in dead_code (#4373)
fixes #4372
2020-12-13 02:24:18 +08:00
Alex Lam S.L
8c000033d3 clarify corner case in object literal (#4371)
closes #4366
2020-12-12 07:42:29 +08:00
Alex Lam S.L
fd0d28e465 fix corner case in spread (#4370) 2020-12-12 06:45:59 +08:00
Alex Lam S.L
2123f38394 fix asynchronous state tracking in ufuzz (#4369) 2020-12-12 05:19:56 +08:00
Alex Lam S.L
58dff9ada3 fix corner cases in unused & varify (#4368)
fixes #4365
2020-12-12 04:45:35 +08:00
Alex Lam S.L
4fdec765bc gate language features in ufuzz automatically (#4367) 2020-12-12 03:43:12 +08:00
Alex Lam S.L
1020d37256 fix corner case in spread (#4364)
fixes #4363
2020-12-12 02:19:11 +08:00
Alex Lam S.L
076739db07 fix corner case in unused (#4362)
fixes #4361
2020-12-12 00:57:05 +08:00
Alex Lam S.L
515e93d88a fix corner case in collapse_vars (#4360)
fixes #4359
2020-12-12 00:07:28 +08:00
Alex Lam S.L
57105b299e fix corner cases with spread syntax (#4358) 2020-12-11 06:59:21 +08:00
Alex Lam S.L
77e1bda426 improve fix for #4355 (#4357) 2020-12-11 00:48:41 +08:00
Alex Lam S.L
a59593cac8 fix corner case in loops & unused (#4356)
fixes #4355
2020-12-10 15:45:39 +08:00
Alex Lam S.L
046bbde9d4 fix corner case in keep_fargs & reduce_vars (#4354)
fixes #4353
2020-12-09 01:41:10 +08:00
Alex Lam S.L
fea9da9866 forbid AST_Await in computed function arguments (#4352)
fixes #4351
2020-12-08 12:59:08 +08:00
Alex Lam S.L
4733159782 fix corner cases with await (#4350)
fixes #4349
2020-12-08 11:26:03 +08:00
Alex Lam S.L
5fba98608c fix corner case in reduce_vars (#4348)
fixes #4347
2020-12-08 08:52:14 +08:00
Alex Lam S.L
c587d7917d introduce spread (#4346)
fixes #4345
2020-12-08 06:51:20 +08:00
Alex Lam S.L
336336f53f fix corner case with parentheses around await (#4344) 2020-12-08 04:29:54 +08:00
Alex Lam S.L
4bde50ce85 fix corner case in side_effects (#4343)
fixes #4342
2020-12-07 17:25:04 +08:00
Alex Lam S.L
fbecedf94c fix corner case in evaluate (#4341)
fixes #4340
2020-12-07 16:05:11 +08:00
Alex Lam S.L
2f31f95095 improve ufuzz (#4339) 2020-12-07 16:04:51 +08:00
Alex Lam S.L
6b603e1a62 fix corner case in unused (#4338)
fixes #4337
2020-12-07 13:23:53 +08:00
Alex Lam S.L
499f8d89ff fix corner case in inline (#4336)
fixes #4335
2020-12-07 11:30:37 +08:00
Alex Lam S.L
9eb65f3af3 extend trailing comma support (#4334) 2020-12-07 10:07:34 +08:00
Alex Lam S.L
2cbbf5c375 support async function (#4333) 2020-12-07 05:22:40 +08:00
Alex Lam S.L
3c384cf9a8 fix corner case in collapse_vars (#4332)
fixes #4331
2020-12-06 18:30:50 +08:00
Alex Lam S.L
37f4f56752 fix corner case in properties (#4330)
fixes #4329
2020-12-06 13:59:04 +08:00
Alex Lam S.L
1e4985ed9e support spread syntax (#4328) 2020-12-06 05:19:31 +08:00
Alex Lam S.L
d2d56e301e v3.12.1 2020-12-01 01:46:27 +08:00
Alex Lam S.L
9d34f8428b fix corner case in side_effects (#4326)
fixes #4325
2020-11-29 10:05:48 +08:00
Alex Lam S.L
f045e2b460 fix corner case in merge_vars (#4324)
fixes #4323
2020-11-29 05:38:24 +08:00
Alex Lam S.L
8791f258e3 fix corner case in inline (#4322)
fixes #4321
2020-11-29 03:48:42 +08:00
Alex Lam S.L
af1cca25bf fix corner case in inline (#4320)
fixes #4319
2020-11-27 01:31:06 +08:00
Alex Lam S.L
9b3a363604 fix infinite recursion in ufuzz (#4318) 2020-11-25 09:33:42 +08:00
Alex Lam S.L
1e8fa1aa1d fix corner case in passes & reduce_vars (#4316)
fixes #4315
2020-11-23 07:05:20 +08:00
24 changed files with 2800 additions and 411 deletions

View File

@@ -4,10 +4,12 @@ UglifyJS 3
UglifyJS is a JavaScript parser, minifier, compressor and beautifier toolkit.
#### Note:
- **`uglify-js@3` has a simplified [API](#api-reference) and [CLI](#command-line-usage) that is not backwards compatible with [`uglify-js@2`](https://github.com/mishoo/UglifyJS/tree/v2.x)**.
- **`uglify-js@3` has a simplified [API](#api-reference) and [CLI](#command-line-usage)
that is not backwards compatible with [`uglify-js@2`](https://github.com/mishoo/UglifyJS/tree/v2.x)**.
- **Documentation for UglifyJS `2.x` releases can be found [here](https://github.com/mishoo/UglifyJS/tree/v2.x)**.
- `uglify-js` only supports JavaScript (ECMAScript 5).
- To minify ECMAScript 2015 or above, transpile using tools like [Babel](https://babeljs.io/).
- `uglify-js` supports ECMAScript 5 and some newer language features.
- To minify ECMAScript 2015 or above, you may need to transpile using tools like
[Babel](https://babeljs.io/).
Install
-------
@@ -751,6 +753,8 @@ to be `false` and all symbol names will be omitted.
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();`
- `spread` (default: `true`) -- flatten spread expressions.
- `strings` (default: `true`) -- compact string concatenations.
- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches
@@ -1173,6 +1177,18 @@ To allow for better optimizations, the compiler makes various assumptions:
- Object properties can be added, removed and modified (not prevented with
`Object.defineProperty()`, `Object.defineProperties()`, `Object.freeze()`,
`Object.preventExtensions()` or `Object.seal()`).
- Earlier versions of JavaScript will throw `SyntaxError` with the following:
```js
({
p: 42,
get p() {},
});
// SyntaxError: Object literal may not have data and accessor property with
// the same name
```
UglifyJS may modify the input which in turn may suppress those errors.
- Iteration order of keys over an object which contains spread syntax in later
versions of Chrome and Node.js may be altered.
- When `toplevel` is enabled, UglifyJS effectively assumes input code is wrapped
within `function(){ ... }`, thus forbids aliasing of declared global variables:
```js

View File

@@ -209,7 +209,9 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
function must_be_expression(node, prop) {
if (!(node[prop] instanceof AST_Node)) throw new Error(prop + " must be AST_Node");
if (node[prop] instanceof AST_Statement && !(node[prop] instanceof AST_Function)) {
if (node[prop] instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
if (node[prop] instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
if (node[prop] instanceof AST_Statement && !is_function(node[prop])) {
throw new Error(prop + " cannot be AST_Statement");
}
}
@@ -278,7 +280,7 @@ var AST_Block = DEFNODE("Block", "body", {
_validate: function() {
this.body.forEach(function(node) {
if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]");
if (node instanceof AST_Function) throw new Error("body cannot contain AST_Function");
if (is_function(node)) throw new Error("body cannot contain AST_Function");
});
},
}, AST_BlockScope);
@@ -294,7 +296,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
},
_validate: function() {
if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement");
if (this.body instanceof AST_Function) throw new Error("body cannot be AST_Function");
if (is_function(this.body)) throw new Error("body cannot be AST_Function");
},
}, AST_BlockScope);
@@ -388,7 +390,7 @@ var AST_For = DEFNODE("For", "init condition step", {
if (this.init != null) {
if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node");
if (this.init instanceof AST_Statement
&& !(this.init instanceof AST_Definitions || this.init instanceof AST_Function)) {
&& !(this.init instanceof AST_Definitions || is_function(this.init))) {
throw new Error("init cannot be AST_Statement");
}
}
@@ -545,6 +547,19 @@ var AST_Accessor = DEFNODE("Accessor", null, {
},
}, AST_Lambda);
function is_function(node) {
return node instanceof AST_AsyncFunction || node instanceof AST_Function;
}
var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined", {
$documentation: "An asynchronous function expression",
_validate: function() {
if (this.name != null) {
if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
}
},
}, AST_Lambda);
var AST_Function = DEFNODE("Function", "inlined", {
$documentation: "A function expression",
_validate: function() {
@@ -554,6 +569,17 @@ var AST_Function = DEFNODE("Function", "inlined", {
},
}, AST_Lambda);
function is_defun(node) {
return node instanceof AST_AsyncDefun || node instanceof AST_Defun;
}
var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined", {
$documentation: "An asynchronous function definition",
_validate: function() {
if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun");
},
}, AST_Lambda);
var AST_Defun = DEFNODE("Defun", "inlined", {
$documentation: "A function definition",
_validate: function() {
@@ -640,7 +666,7 @@ var AST_If = DEFNODE("If", "condition alternative", {
must_be_expression(this, "condition");
if (this.alternative != null) {
if (!(this.alternative instanceof AST_Statement)) throw new Error("alternative must be AST_Statement");
if (this.alternative instanceof AST_Function) throw new error("alternative cannot be AST_Function");
if (is_function(this.alternative)) throw new error("alternative cannot be AST_Function");
}
},
}, AST_StatementWithBody);
@@ -817,10 +843,12 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
/* -----[ OTHER ]----- */
function must_be_expressions(node, prop) {
function must_be_expressions(node, prop, allow_spread, allow_hole) {
node[prop].forEach(function(node) {
if (!(node instanceof AST_Node)) throw new Error(prop + " must be AST_Node[]");
if (node instanceof AST_Statement && !(node instanceof AST_Function)) {
if (!allow_hole && node instanceof AST_Hole) throw new Error(prop + " cannot be AST_Hole");
if (!allow_spread && node instanceof AST_Spread) throw new Error(prop + " cannot be AST_Spread");
if (node instanceof AST_Statement && !is_function(node)) {
throw new Error(prop + " cannot contain AST_Statement");
}
});
@@ -843,7 +871,7 @@ var AST_Call = DEFNODE("Call", "expression args pure", {
},
_validate: function() {
must_be_expression(this, "expression");
must_be_expressions(this, "args");
must_be_expressions(this, "args", true);
},
});
@@ -920,6 +948,22 @@ var AST_Sub = DEFNODE("Sub", null, {
},
}, AST_PropAccess);
var AST_Spread = DEFNODE("Spread", "expression", {
$documentation: "Spread expression in array/object literals or function calls",
$propdoc: {
expression: "[AST_Node] expression to be expanded",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
node.expression.walk(visitor);
});
},
_validate: function() {
must_be_expression(this, "expression");
},
});
var AST_Unary = DEFNODE("Unary", "operator expression", {
$documentation: "Base class for unary expressions",
$propdoc: {
@@ -1004,6 +1048,22 @@ var AST_Assign = DEFNODE("Assign", null, {
},
}, AST_Binary);
var AST_Await = DEFNODE("Await", "expression", {
$documentation: "An await expression",
$propdoc: {
expression: "[AST_Node] expression with Promise to resolve on",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
node.expression.walk(visitor);
});
},
_validate: function() {
must_be_expression(this, "expression");
},
});
/* -----[ LITERALS ]----- */
var AST_Array = DEFNODE("Array", "elements", {
@@ -1020,7 +1080,7 @@ var AST_Array = DEFNODE("Array", "elements", {
});
},
_validate: function() {
must_be_expressions(this, "elements");
must_be_expressions(this, "elements", true, true);
},
});
@@ -1098,7 +1158,7 @@ var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
var AST_Object = DEFNODE("Object", "properties", {
$documentation: "An object literal",
$propdoc: {
properties: "[AST_ObjectProperty*] array of properties"
properties: "[(AST_ObjectProperty|AST_Spread)*] array of properties"
},
walk: function(visitor) {
var node = this;
@@ -1110,7 +1170,9 @@ var AST_Object = DEFNODE("Object", "properties", {
},
_validate: function() {
this.properties.forEach(function(node) {
if (!(node instanceof AST_ObjectProperty)) throw new Error("properties must be AST_ObjectProperty[]");
if (!(node instanceof AST_ObjectProperty || node instanceof AST_Spread)) {
throw new Error("properties must contain AST_ObjectProperty and/or AST_Spread only");
}
});
},
});
@@ -1389,14 +1451,13 @@ TreeWalker.prototype = {
|| p.tail_node() === self) {
self = p;
} else if (p instanceof AST_Return) {
var fn;
do {
fn = this.parent(++i);
if (!fn) return false;
} while (!(fn instanceof AST_Lambda));
if (fn.name) return false;
self = this.parent(++i);
if (!self || self.TYPE != "Call" || self.expression !== fn) return false;
for (var call, fn = p; call = this.parent(++i); fn = call) {
if (call.TYPE == "Call") {
if (!(fn instanceof AST_Lambda) || fn.name) return false;
} else if (fn instanceof AST_Lambda) {
return false;
}
}
} else {
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -661,7 +661,7 @@ function OutputStream(options) {
// a function expression needs parens around it when it's provably
// the first token to appear in a statement.
PARENS(AST_Function, function(output) {
function needs_parens_function(output) {
if (!output.has_parens() && first_in_statement(output)) return true;
if (output.option("webkit")) {
var p = output.parent();
@@ -671,7 +671,9 @@ function OutputStream(options) {
var p = output.parent();
if (p instanceof AST_Call && p.expression === this) return true;
}
});
}
PARENS(AST_AsyncFunction, needs_parens_function);
PARENS(AST_Function, needs_parens_function);
// same goes for an object literal, because otherwise it would be
// interpreted as a block of code.
@@ -689,6 +691,8 @@ function OutputStream(options) {
var p = output.parent();
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
return p instanceof AST_Array
// await (foo, bar)
|| p instanceof AST_Await
// 1 + (2, 3) + 4 ==> 8
|| p instanceof AST_Binary
// new (foo, bar) or foo(1, (2, 3), 4)
@@ -702,6 +706,8 @@ function OutputStream(options) {
|| p instanceof AST_ObjectProperty
// (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
@@ -710,6 +716,8 @@ function OutputStream(options) {
PARENS(AST_Binary, function(output) {
var p = output.parent();
// await (foo && bar)
if (p instanceof AST_Await) return true;
// this deals with precedence: 3 * (2 + 1)
if (p instanceof AST_Binary) {
var po = p.operator, pp = PRECEDENCE[po];
@@ -777,6 +785,8 @@ function OutputStream(options) {
function needs_parens_assign_cond(self, output) {
var p = output.parent();
// await (a = foo)
if (p instanceof AST_Await) return true;
// 1 + (a = 2) + 3 → 6, side effect setting a = 2
if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
// (a = func)() —or— new (a = Object)()
@@ -800,6 +810,16 @@ function OutputStream(options) {
return needs_parens_assign_cond(this, output);
});
PARENS(AST_Await, function(output) {
var p = output.parent();
// new (await foo)
// (await foo)(bar)
if (p instanceof AST_Call) return p.expression === this;
// (await foo).prop
// (await foo)["prop"]
if (p instanceof AST_PropAccess) return p.expression === this;
});
/* -----[ PRINTERS ]----- */
DEFPRINT(AST_Directive, function(output) {
@@ -965,11 +985,7 @@ function OutputStream(options) {
});
/* -----[ functions ]----- */
DEFPRINT(AST_Lambda, function(output, nokeyword) {
var self = this;
if (!nokeyword) {
output.print("function");
}
function print_lambda(self, output) {
if (self.name) {
output.space();
self.name.print(output);
@@ -982,7 +998,19 @@ function OutputStream(options) {
});
output.space();
print_braced(self, output, true);
}
DEFPRINT(AST_Lambda, function(output) {
output.print("function");
print_lambda(this, output);
});
function print_async(output) {
output.print("async");
output.space();
output.print("function");
print_lambda(this, output);
}
DEFPRINT(AST_AsyncDefun, print_async);
DEFPRINT(AST_AsyncFunction, print_async);
/* -----[ jumps ]----- */
function print_jump(kind, prop) {
@@ -1231,6 +1259,10 @@ function OutputStream(options) {
this.property.print(output);
output.print("]");
});
DEFPRINT(AST_Spread, function(output) {
output.print("...");
this.expression.print(output);
});
DEFPRINT(AST_UnaryPrefix, function(output) {
var op = this.operator;
var exp = this.expression;
@@ -1266,6 +1298,11 @@ function OutputStream(options) {
output.colon();
self.alternative.print(output);
});
DEFPRINT(AST_Await, function(output) {
output.print("await");
output.space();
this.expression.print(output);
});
/* -----[ literals ]----- */
DEFPRINT(AST_Array, function(output) {
@@ -1369,7 +1406,7 @@ function OutputStream(options) {
output.print(type);
output.space();
print_property_key(self, output);
self.value._codegen(output, true);
print_lambda(self.value, output);
};
}
DEFPRINT(AST_ObjectGetter, print_accessor("get"));

View File

@@ -47,7 +47,7 @@
var KEYWORDS = "break case catch const continue debugger default delete do else finally for function if in instanceof let new return switch throw try typeof var void while with";
var KEYWORDS_ATOM = "false null true";
var RESERVED_WORDS = [
"abstract boolean byte char class double enum export extends final float goto implements import int interface let long native package private protected public short static super synchronized this throws transient volatile yield",
"await abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield",
KEYWORDS_ATOM,
KEYWORDS,
].join(" ");
@@ -501,7 +501,16 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
function handle_dot() {
next();
return is_digit(peek().charCodeAt(0)) ? read_num(".") : token("punc", ".");
var ch = peek();
if (ch == ".") {
var op = ".";
do {
op += ".";
next();
} while (peek() == ".");
return token("operator", op);
}
return is_digit(ch.charCodeAt(0)) ? read_num(".") : token("punc", ".");
}
function read_word() {
@@ -644,13 +653,15 @@ function parse($TEXT, options) {
input : typeof $TEXT == "string"
? tokenizer($TEXT, options.filename, options.html5_comments, options.shebang)
: $TEXT,
token : null,
prev : null,
peeked : null,
in_function : 0,
in_async : false,
in_directives : true,
in_funarg : -1,
in_function : 0,
in_loop : 0,
labels : []
labels : [],
peeked : null,
prev : null,
token : null,
};
S.token = next();
@@ -777,9 +788,20 @@ function parse($TEXT, options) {
return simple_statement();
case "name":
return is_token(peek(), "punc", ":")
? labeled_statement()
: simple_statement();
switch (S.token.value) {
case "async":
if (is_token(peek(), "keyword", "function")) {
next();
next();
return function_(AST_AsyncDefun);
}
case "await":
if (S.in_async) return simple_statement();
default:
return is_token(peek(), "punc", ":")
? labeled_statement()
: simple_statement();
}
case "punc":
switch (S.token.value) {
@@ -1017,19 +1039,27 @@ function parse($TEXT, options) {
}
var function_ = function(ctor) {
var in_statement = ctor === AST_Defun;
var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null;
if (in_statement && !name)
expect_token("name");
var was_async = S.in_async;
var name;
if (ctor === AST_AsyncDefun) {
name = as_symbol(AST_SymbolDefun);
S.in_async = true;
} else if (ctor === AST_Defun) {
name = as_symbol(AST_SymbolDefun);
S.in_async = false;
} else {
S.in_async = ctor === AST_AsyncFunction;
name = as_symbol(AST_SymbolLambda, true);
}
if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration))
unexpected(prev());
expect("(");
var argnames = [];
for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(",");
argnames.push(maybe_destructured(AST_SymbolFunarg));
}
next();
var was_funarg = S.in_funarg;
S.in_funarg = S.in_function;
var argnames = expr_list(")", !options.strict, false, function() {
return maybe_destructured(AST_SymbolFunarg);
});
S.in_funarg = was_funarg;
var loop = S.in_loop;
var labels = S.labels;
++S.in_function;
@@ -1046,6 +1076,7 @@ function parse($TEXT, options) {
--S.in_function;
S.in_loop = loop;
S.labels = labels;
S.in_async = was_async;
return new ctor({
name: name,
argnames: argnames,
@@ -1203,7 +1234,7 @@ function parse($TEXT, options) {
var newexp = expr_atom(false), args;
if (is("punc", "(")) {
next();
args = expr_list(")");
args = expr_list(")", !options.strict);
} else {
args = [];
}
@@ -1295,9 +1326,16 @@ function parse($TEXT, options) {
}
unexpected();
}
if (is("keyword", "function")) {
var ctor;
if (is("name", "async") && is_token(peek(), "keyword", "function")) {
next();
var func = function_(AST_Function);
ctor = AST_AsyncFunction;
} else if (is("keyword", "function")) {
ctor = AST_Function;
}
if (ctor) {
next();
var func = function_(ctor);
func.start = start;
func.end = prev();
return subscripts(func, allow_calls);
@@ -1314,8 +1352,14 @@ function parse($TEXT, options) {
while (!is("punc", closing)) {
if (first) first = false; else expect(",");
if (allow_trailing_comma && is("punc", closing)) break;
if (is("punc", ",") && allow_empty) {
if (allow_empty && is("punc", ",")) {
a.push(new AST_Hole({ start: S.token, end: S.token }));
} else if (parser === expression && is("operator", "...")) {
a.push(new AST_Spread({
start: S.token,
expression: (next(), parser()),
end: prev(),
}));
} else {
a.push(parser());
}
@@ -1343,6 +1387,15 @@ function parse($TEXT, options) {
// allow trailing comma
if (!options.strict && is("punc", "}")) break;
var start = S.token;
if (is("operator", "...")) {
next();
a.push(new AST_Spread({
start: start,
expression: expression(false),
end: prev(),
}));
continue;
}
var key = as_property_key();
if (is("punc", "(")) {
var func_start = S.token;
@@ -1427,6 +1480,7 @@ function parse($TEXT, options) {
function _make_symbol(type, token) {
var name = token.value;
if (name === "await" && S.in_async) unexpected(token);
return new (name === "this" ? AST_This : type)({
name: "" + name,
start: token,
@@ -1540,7 +1594,7 @@ function parse($TEXT, options) {
var call = new AST_Call({
start : start,
expression : expr,
args : expr_list(")"),
args : expr_list(")", !options.strict),
end : prev()
});
mark_pure(call);
@@ -1549,17 +1603,17 @@ function parse($TEXT, options) {
return expr;
};
var maybe_unary = function(allow_calls) {
function maybe_unary() {
var start = S.token;
if (is("operator") && UNARY_PREFIX[start.value]) {
next();
handle_regexp();
var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(allow_calls));
var ex = make_unary(AST_UnaryPrefix, start, maybe_await());
ex.start = start;
ex.end = prev();
return ex;
}
var val = expr_atom(allow_calls);
var val = expr_atom(true);
while (is("operator") && UNARY_POSTFIX[S.token.value] && !has_newline_before(S.token)) {
val = make_unary(AST_UnaryPostfix, S.token, val);
val.start = start;
@@ -1567,7 +1621,7 @@ function parse($TEXT, options) {
next();
}
return val;
};
}
function make_unary(ctor, token, expr) {
var op = token.value;
@@ -1585,13 +1639,26 @@ function parse($TEXT, options) {
return new ctor({ operator: op, expression: expr });
}
function maybe_await() {
var start = S.token;
if (!(S.in_async && is("name", "await"))) return maybe_unary();
if (S.in_funarg === S.in_function) croak("Invalid use of await in function argument");
S.input.context().regex_allowed = true;
next();
return new AST_Await({
start: start,
expression: maybe_await(),
end: prev(),
});
}
var expr_op = function(left, min_prec, no_in) {
var op = is("operator") ? S.token.value : null;
if (op == "in" && no_in) op = null;
var prec = op != null ? PRECEDENCE[op] : null;
if (prec != null && prec > min_prec) {
next();
var right = expr_op(maybe_unary(true), prec, no_in);
var right = expr_op(maybe_await(), prec, no_in);
return expr_op(new AST_Binary({
start : left.start,
left : left,
@@ -1604,7 +1671,7 @@ function parse($TEXT, options) {
};
function expr_ops(no_in) {
return expr_op(maybe_unary(true), 0, no_in);
return expr_op(maybe_await(), 0, no_in);
}
var maybe_conditional = function(no_in) {

View File

@@ -112,7 +112,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var next_def_id = 0;
var scope = self.parent_scope = null;
var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_Defun) {
if (is_defun(node)) {
node.name.walk(tw);
walk_scope(function() {
node.argnames.forEach(function(argname) {
@@ -190,7 +190,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
function entangle(defun, scope) {
if (defun === scope) return;
node.mark_enclosed(options);
var def = scope.find_variable(node);
var def = scope.find_variable(node.name);
if (node.thedef === def) return;
node.thedef = def;
def.orig.push(node);
@@ -219,6 +219,21 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (node.label) node.label.thedef.references.push(node);
return true;
}
// ensure mangling works if `catch` reuses a scope variable
if (node instanceof AST_SymbolCatch) {
var def = node.definition().redefined();
if (def) for (var s = node.scope; s; s = s.parent_scope) {
push_uniq(s.enclosed, def);
if (s === def.scope) break;
}
return true;
}
// ensure compression works if `const` reuses a scope variable
if (node instanceof AST_SymbolConst) {
var redef = node.definition().redefined();
if (redef) redef.const_redefs = true;
return true;
}
if (node instanceof AST_SymbolRef) {
var name = node.name;
var sym = node.scope.find_variable(name);
@@ -258,21 +273,6 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
node.reference(options);
return true;
}
// ensure mangling works if `catch` reuses a scope variable
if (node instanceof AST_SymbolCatch) {
var def = node.definition().redefined();
if (def) for (var s = node.scope; s; s = s.parent_scope) {
push_uniq(s.enclosed, def);
if (s === def.scope) break;
}
return true;
}
// ensure compression works if `const` reuses a scope variable
if (node instanceof AST_SymbolConst) {
var redef = node.definition().redefined();
if (redef) redef.const_redefs = true;
return true;
}
});
self.walk(tw);
@@ -390,14 +390,13 @@ AST_Symbol.DEFMETHOD("reference", function(options) {
});
AST_BlockScope.DEFMETHOD("find_variable", function(name) {
if (name instanceof AST_Symbol) name = name.name;
return this.variables.get(name)
|| (this.parent_scope && this.parent_scope.find_variable(name));
|| this.parent_scope && this.parent_scope.find_variable(name);
});
AST_BlockScope.DEFMETHOD("def_function", function(symbol, init) {
var def = this.def_variable(symbol, init);
if (!def.init || def.init instanceof AST_Defun) def.init = init;
if (!def.init || is_defun(def.init)) def.init = init;
this.functions.set(symbol.name, def);
return def;
});
@@ -406,7 +405,7 @@ AST_BlockScope.DEFMETHOD("def_variable", function(symbol, init) {
var def = this.variables.get(symbol.name);
if (def) {
def.orig.push(symbol);
if (def.init instanceof AST_Function) def.init = init;
if (is_function(def.init)) def.init = init;
} else {
def = this.make_def(symbol, init);
this.variables.set(symbol.name, def);

View File

@@ -138,6 +138,9 @@ TreeTransformer.prototype = new TreeWalker;
DEF(AST_Sequence, function(self, tw) {
self.expressions = do_list(self.expressions, tw);
});
DEF(AST_Await, function(self, tw) {
self.expression = self.expression.transform(tw);
});
DEF(AST_Dot, function(self, tw) {
self.expression = self.expression.transform(tw);
});
@@ -145,6 +148,9 @@ TreeTransformer.prototype = new TreeWalker;
self.expression = self.expression.transform(tw);
self.property = self.property.transform(tw);
});
DEF(AST_Spread, function(self, tw) {
self.expression = self.expression.transform(tw);
});
DEF(AST_Unary, function(self, tw) {
self.expression = self.expression.transform(tw);
});

View File

@@ -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.0",
"version": "3.12.2",
"engines": {
"node": ">=0.8.0"
},

498
test/compress/async.js Normal file
View File

@@ -0,0 +1,498 @@
await_await: {
input: {
(async function() {
console.log("PASS");
await await 42;
})();
}
expect: {
(async function() {
console.log("PASS");
await await 42;
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
defun_name: {
input: {
async function await() {
console.log("PASS");
}
await();
}
expect: {
async function await() {
console.log("PASS");
}
await();
}
expect_stdout: "PASS"
node_version: ">=8"
}
nested_await: {
input: {
(async function() {
console.log(function(await) {
return await;
}("PASS"));
})();
}
expect: {
(async function() {
console.log(function(await) {
return await;
}("PASS"));
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
reduce_single_use_defun: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
async function f(a) {
console.log(a);
}
f("PASS");
}
expect: {
(async function(a) {
console.log(a);
})("PASS");
}
expect_stdout: "PASS"
node_version: ">=8"
}
dont_inline: {
options = {
inline: true,
}
input: {
(async function() {
A;
})().catch(function() {});
console.log("PASS");
}
expect: {
(async function() {
A;
})().catch(function() {});
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=8"
}
evaluate: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = async function() {}();
console.log(typeof a);
}
expect: {
var a = async function() {}();
console.log(typeof a);
}
expect_stdout: "object"
node_version: ">=8"
}
negate: {
options = {
side_effects: true,
}
input: {
console && async function() {} && console.log("PASS");
}
expect: {
console && async function() {} && console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=8"
}
negate_iife: {
options = {
negate_iife: true,
}
input: {
(async function() {
console.log("PASS");
})();
}
expect: {
!async function() {
console.log("PASS");
}();
}
expect_stdout: "PASS"
node_version: ">=8"
}
collapse_vars_1: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
(async function() {
a = "PASS";
await 42;
return "PASS";
})();
console.log(a);
}
expect: {
var a = "FAIL";
(async function() {
a = "PASS";
await 42;
return "PASS";
})();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=8"
}
collapse_vars_2: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
(async function() {
await (a = "PASS");
return "PASS";
})();
console.log(a);
}
expect: {
var a = "FAIL";
(async function() {
await (a = "PASS");
return "PASS";
})();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=8"
}
collapse_vars_3: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
(async function() {
await (a = "PASS", 42);
return "PASS";
})();
console.log(a);
}
expect: {
var a = "FAIL";
(async function() {
await (a = "PASS", 42);
return "PASS";
})();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4335_1: {
options = {
inline: true,
}
input: {
var await = "PASS";
(async function() {
console.log(function() {
return await;
}());
})();
}
expect: {
var await = "PASS";
(async function() {
console.log(function() {
return await;
}());
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4335_2: {
options = {
inline: true,
}
input: {
(async function() {
console.log(function() {
function await() {}
return "PASS";
}());
})();
}
expect: {
(async function() {
console.log(function() {
function await() {}
return "PASS";
}());
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4337: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
a();
})(async function() {
console.log("PASS");
});
}
expect: {
(function(a) {
(async function() {
console.log("PASS");
})();
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4340: {
options = {
evaluate: true,
reduce_vars: true,
}
input: {
(async function a(a) {
console.log(a || "PASS");
})();
}
expect: {
(async function a(a) {
console.log(a || "PASS");
})();
}
expect_stdout: "PASS"
node_version: ">=8"
}
call_expression: {
input: {
console.log(typeof async function(log) {
(await log)("FAIL");
}(console.log).then);
}
expect_exact: 'console.log(typeof async function(log){(await log)("FAIL")}(console.log).then);'
expect_stdout: "function"
node_version: ">=8"
}
property_access_expression: {
input: {
console.log(typeof async function(con) {
(await con).log("FAIL");
}(console).then);
}
expect_exact: 'console.log(typeof async function(con){(await con).log("FAIL")}(console).then);'
expect_stdout: "function"
node_version: ">=8"
}
issue_4347_1: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "foo";
f();
a = "bar";
f();
async function f() {
console.log(a);
}
}
expect: {
var a = "foo";
f();
a = "bar";
f();
async function f() {
console.log(a);
}
}
expect_stdout: [
"foo",
"bar",
]
node_version: ">=8"
}
issue_4347_2: {
options = {
evaluate: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = "PASS";
(async function() {
throw 42;
a = "FAIL";
})();
console.log(a);
}
expect: {
var a = "PASS";
(async function() {
throw 42;
a = "FAIL";
})();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4349_1: {
input: {
console.log(typeof async function() {
await /abc/;
}().then);
}
expect_exact: "console.log(typeof async function(){await/abc/}().then);"
expect_stdout: "function"
node_version: ">=8"
}
issue_4349_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(typeof async function() {
(function(a) {
this[a];
}(await 0));
}().then);
}
expect: {
console.log(typeof async function() {
(function(a) {
this[a];
}(await 0));
}().then);
}
expect_stdout: "function"
node_version: ">=8"
}
issue_4349_3: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(typeof function(await) {
return async function(a) {
this[a];
}(await);
}(this).then);
}
expect: {
console.log(typeof function(await) {
return async function(a) {
this[a];
}(await);
}(this).then);
}
expect_stdout: "function"
node_version: ">=8"
}
issue_4359: {
options = {
collapse_vars: true,
unused: true,
}
input: {
try {
(async function(a) {
return a;
})(A);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
(async function(a) {
return a;
})(A);
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4377: {
options = {
dead_code: true,
inline: true,
side_effects: true,
}
input: {
console.log(typeof function() {
return function() {
f;
async function f() {}
return f();
}();
}().then);
}
expect: {
console.log(typeof function() {
return f();
async function f() {}
}().then);
}
expect_stdout: "function"
node_version: ">=8"
}

View File

@@ -153,3 +153,31 @@ issue_3690: {
}
expect_stdout: "PASS"
}
issue_4374: {
options = {
booleans: true,
conditionals: true,
if_return: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
console.log(f());
function f(a) {
if (null) return 0;
if (a) return 1;
return 0;
}
})();
}
expect: {
(function() {
console.log(function(a) {
return !null && a ? 1 : 0;
}());
})();
}
expect_stdout: "0"
}

View File

@@ -1347,3 +1347,31 @@ issue_4305_2: {
}
expect_stdout: true
}
issue_4365_1: {
options = {
toplevel: true,
unused: true,
}
input: {
const arguments = 42;
}
expect: {
const arguments = 42;
}
expect_stdout: true
}
issue_4365_2: {
options = {
toplevel: true,
varify: true,
}
input: {
const arguments = 42;
}
expect: {
const arguments = 42;
}
expect_stdout: true
}

View File

@@ -1375,3 +1375,27 @@ issue_4051: {
}
expect_stdout: "PASS"
}
issue_4366: {
options = {
dead_code: true,
}
input: {
function f() {
return "PASS";
({
p: 42,
get p() {},
});
}
console.log(f());
}
expect: {
function f() {
return "PASS";
}
console.log(f());
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -1647,3 +1647,200 @@ issue_4312: {
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4315: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
inline: true,
passes: 2,
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
function f() {
console;
}
var a = function() {
if ([ 0[f && f] ] = [])
return this;
}(), b;
do {
console.log("PASS");
} while (0 && (b = 0), b && a);
}
expect: {
[ 0[function() {
console
}] ] = [];
do {
console.log("PASS");
} while (void 0);
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4319: {
options = {
inline: true,
reduce_vars: true,
toplevel: true,
}
input: {
function f(a) {
while (!a);
}
console.log(function({}) {
return f(console);
}(0));
}
expect: {
function f(a) {
while (!a);
}
console.log(function({}) {
return f(console);
}(0));
}
expect_stdout: "undefined"
node_version: ">=6"
}
issue_4321: {
options = {
inline: true,
keep_fargs: "strict",
}
input: {
try {
console.log(function({}) {
return function() {
while (!console);
}();
}());
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
console.log(function({}) {
return function() {
while (!console);
}();
}());
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4323: {
options = {
ie8: true,
inline: true,
merge_vars: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
var a = 0;
(function b({
[function a() {
console.log(typeof a);
}()]: d,
}) {})(0);
(function c(e) {
e.p;
})(1, console.log);
}
expect: {
var a = 0;
(function({
[function a() {
console.log(typeof a);
}()]: d,
}) {})(0);
e = 1,
console.log,
void e.p;
var e;
}
expect_stdout: "function"
node_version: ">=6"
}
issue_4355: {
options = {
loops: true,
unused: true,
}
input: {
var a;
(function({
[function() {
for (a in "foo");
}()]: b,
}) {
var a;
})(0);
console.log(a);
}
expect: {
var a;
(function({
[function() {
for (a in "foo");
}()]: b,
}) {})(0);
console.log(a);
}
expect_stdout: "2"
node_version: ">=6"
}
issue_4372_1: {
options = {
dead_code: true,
}
input: {
var a = "FAIL";
a += {
[console.log(a)]: a,
} = a = "PASS";
}
expect: {
var a = "FAIL";
a += {
[console.log(a)]: a,
} = a = "PASS";
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4372_2: {
options = {
dead_code: true,
}
input: {
var a;
[ a ] = a = [ "PASS", "FAIL" ];
console.log(a);
}
expect: {
var a;
[ a ] = [ "PASS", "FAIL" ];
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=6"
}

View File

@@ -3322,9 +3322,7 @@ issue_3506_1: {
}
expect: {
var a = "FAIL";
!function(b) {
b && (a = "PASS");
}(a);
a && (a = "PASS");
console.log(a);
}
expect_stdout: "PASS"
@@ -5215,3 +5213,13 @@ issue_4265: {
}
expect_stdout: "undefined"
}
trailing_comma: {
input: {
new function(a, b,) {
console.log(b, a,);
}(42, "PASS",);
}
expect_exact: 'new function(a,b){console.log(b,a)}(42,"PASS");'
expect_stdout: "PASS 42"
}

View File

@@ -588,7 +588,6 @@ issue_3197_1: {
ie8: false,
}
input: {
var window = {};
!function() {
function Foo() {
console.log(this instanceof Foo);
@@ -598,7 +597,6 @@ issue_3197_1: {
new window.Foo();
}
expect: {
var window = {};
window.Foo = function o() {
console.log(this instanceof o);
};
@@ -619,7 +617,6 @@ issue_3197_1_ie8: {
ie8: true,
}
input: {
var window = {};
!function() {
function Foo() {
console.log(this instanceof Foo);
@@ -629,7 +626,6 @@ issue_3197_1_ie8: {
new window.Foo();
}
expect: {
var window = {};
window.Foo = function Foo() {
console.log(this instanceof Foo);
};

View File

@@ -525,7 +525,7 @@ issue_2506: {
function f0(bar) {
(function() {
(function() {
if (false <= 0/0 & this >> 1 >= 0)
if (false <= NaN & this >> 1 >= 0)
c++;
})(c++);
})();
@@ -1452,3 +1452,37 @@ issue_3619: {
}
expect_stdout: "PASS"
}
issue_4353_1: {
options = {
keep_fargs: "strict",
reduce_vars: true,
unused: true,
}
input: {
console.log(function f(a) {}.length);
}
expect: {
console.log(function(a) {}.length);
}
expect_stdout: "1"
}
issue_4353_2: {
options = {
keep_fargs: "strict",
reduce_vars: true,
unused: true,
}
input: {
(function f(a) {
while (console.log("PASS"));
})();
}
expect: {
(function() {
while (console.log("PASS"));
})();
}
expect_stdout: "PASS"
}

View File

@@ -1052,6 +1052,7 @@ issue_4084: {
options = {
keep_fargs: "strict",
loops: true,
passes: 2,
reduce_vars: true,
unused: true,
}
@@ -1254,3 +1255,28 @@ issue_4240: {
}
expect_stdout: "PASS"
}
issue_4355: {
options = {
dead_code: true,
evaluate: true,
loops: true,
side_effects: true,
unused: true,
}
input: {
while (function() {
var a;
for (a in console.log("PASS"))
var b = 0;
}())
var c;
}
expect: {
(function() {
console.log("PASS");
})();
var c;
}
expect_stdout: "PASS"
}

View File

@@ -45,8 +45,8 @@ duplicate_key_strict: {
"use strict";
var o = {
a: 1,
b: 2,
a: 3,
b: 2,
};
for (var k in o)
console.log(k, o[k]);
@@ -323,8 +323,8 @@ issue_4269_3: {
}
expect: {
console.log({
["foo"]: "bar",
get 42() {
foo: "bar",
get [42]() {
return "FAIL";
},
42: "PASS",
@@ -353,10 +353,63 @@ issue_4269_4: {
get 42() {
return "FAIL";
},
["foo"]: "bar",
foo: "bar",
[42]: "PASS",
}[42]);
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4269_5: {
options = {
evaluate: true,
objects: true,
}
input: {
console.log({
get 42() {
return "FAIL";
},
[console]: "bar",
42: "PASS",
}[42]);
}
expect: {
console.log({
get 42() {
return "FAIL";
},
[console]: "bar",
42: "PASS",
}[42]);
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4380: {
options = {
evaluate: true,
objects: true,
}
input: {
console.log({
get 0() {
return "FAIL 1";
},
0: "FAIL 2",
[0]: "PASS",
}[0]);
}
expect: {
console.log({
get 0() {
return "FAIL 1";
},
[0]: ("FAIL 2", "PASS"),
}[0]);
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -433,3 +433,76 @@ trim_new: {
}
expect_stdout: "PASS"
}
issue_4325: {
options = {
keep_fargs: "strict",
passes: 2,
pure_getters: "strict",
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
(function f() {
(function(b, c) {
try {
c.p = 0;
} catch (e) {
console.log("PASS");
return b;
}
c;
})(f++);
})();
}
expect: {
(function() {
(function() {
try {
(void 0).p = 0;
} catch (e) {
console.log("PASS");
return;
}
})();
})();
}
expect_stdout: "PASS"
}
issue_4366_1: {
options = {
side_effects: true,
}
input: {
({
p: 42,
get p() {},
q: console.log("PASS"),
});
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4366_2: {
options = {
side_effects: true,
}
input: {
({
set p(v) {},
q: console.log("PASS"),
p: 42,
});
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=4"
}

806
test/compress/spread.js Normal file
View File

@@ -0,0 +1,806 @@
collapse_vars_1: {
options = {
collapse_vars: true,
}
input: {
var a;
[ ...a = "PASS", "PASS"].slice();
console.log(a);
}
expect: {
var a;
[ ...a = "PASS", "PASS"].slice();
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_vars_2: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
try {
a = "PASS";
[ ...42, "PASS"].slice();
} catch (e) {
console.log(a);
}
}
expect: {
var a = "FAIL";
try {
a = "PASS";
[ ...42, "PASS"].slice();
} catch (e) {
console.log(a);
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_vars_3: {
options = {
collapse_vars: true,
}
input: {
var a = "FAIL";
try {
[ ...(a = "PASS", 42), "PASS"].slice();
} catch (e) {
console.log(a);
}
}
expect: {
var a = "FAIL";
try {
[ ...(a = "PASS", 42), "PASS"].slice();
} catch (e) {
console.log(a);
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_vars_4: {
options = {
collapse_vars: true,
unused: true,
}
input: {
console.log(function(a) {
return a;
}(...[ "PASS", "FAIL" ]));
}
expect: {
console.log(function(a) {
return a;
}(...[ "PASS", "FAIL" ]));
}
expect_stdout: "PASS"
node_version: ">=6"
}
dont_inline: {
options = {
inline: true,
}
input: {
console.log(function(a) {
return a;
}(...[ "PASS", "FAIL" ]));
}
expect: {
console.log(function(a) {
return a;
}(...[ "PASS", "FAIL" ]));
}
expect_stdout: "PASS"
node_version: ">=6"
}
do_inline: {
options = {
inline: true,
spread: true,
}
input: {
console.log(function(a) {
return a;
}(...[ "PASS", "FAIL" ]));
}
expect: {
console.log(("FAIL", "PASS"));
}
expect_stdout: "PASS"
node_version: ">=6"
}
drop_empty_call_1: {
options = {
side_effects: true,
}
input: {
try {
(function() {})(...null);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
[ ...null ];
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
drop_empty_call_2: {
options = {
side_effects: true,
spread: true,
}
input: {
(function() {})(...[ console.log("PASS") ]);
}
expect: {
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=6"
}
convert_hole: {
options = {
spread: true,
}
input: {
console.log(...[ "PASS", , 42 ]);
}
expect: {
console.log("PASS", void 0, 42);
}
expect_stdout: "PASS undefined 42"
node_version: ">=6"
}
keep_property_access: {
options = {
properties: true,
side_effects: true,
}
input: {
console.log(function() {
return [ ..."foo" ][0];
}());
}
expect: {
console.log(function() {
return [ ..."foo" ][0];
}());
}
expect_stdout: "f"
node_version: ">=6"
}
keep_fargs: {
options = {
keep_fargs: "strict",
unused: true,
}
input: {
var a = [ "PASS" ];
(function(b, c) {
console.log(c);
})(console, ...a);
}
expect: {
var a = [ "PASS" ];
(function(b, c) {
console.log(c);
})(console, ...a);
}
expect_stdout: "PASS"
node_version: ">=6"
}
reduce_vars_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
console.log(function(b, c) {
return c ? "PASS" : "FAIL";
}(..."foo"));
}
expect: {
console.log(function(b, c) {
return c ? "PASS" : "FAIL";
}(..."foo"));
}
expect_stdout: "PASS"
node_version: ">=6"
}
reduce_vars_2: {
options = {
conditionals: true,
evaluate: true,
reduce_vars: true,
}
input: {
console.log(function(b, c) {
return c ? "PASS" : "FAIL";
}(..."foo"));
}
expect: {
console.log(function(b, c) {
return c ? "PASS" : "FAIL";
}(..."foo"));
}
expect_stdout: "PASS"
node_version: ">=6"
}
convert_setter: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
...{
set PASS(v) {},
},
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
PASS: void 0,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: "PASS undefined"
node_version: ">=8"
}
keep_getter_1: {
options = {
side_effects: true,
}
input: {
({
...{
get p() {
console.log("PASS");
},
},
get q() {
console.log("FAIL");
},
});
}
expect: {
({
...{
get p() {
console.log("PASS");
},
},
});
}
expect_stdout: "PASS"
node_version: ">=8"
}
keep_getter_2: {
options = {
side_effects: true,
}
input: {
({
...(console.log("foo"), {
get p() {
console.log("bar");
},
}),
});
}
expect: {
({
...(console.log("foo"), {
get p() {
console.log("bar");
},
}),
});
}
expect_stdout: [
"foo",
"bar",
]
node_version: ">=8"
}
keep_getter_3: {
options = {
side_effects: true,
}
input: {
({
...function() {
return {
get p() {
console.log("PASS");
},
};
}(),
});
}
expect: {
({
...function() {
return {
get p() {
console.log("PASS");
},
};
}(),
});
}
expect_stdout: "PASS"
node_version: ">=8"
}
keep_getter_4: {
options = {
reduce_vars: true,
side_effects: true,
toplevel: true,
}
input: {
var o = {
get p() {
console.log("PASS");
},
};
({
q: o,
...o,
});
}
expect: {
var o = {
get p() {
console.log("PASS");
},
};
({
...o,
});
}
expect_stdout: "PASS"
node_version: ">=8"
}
keep_accessor: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
...{
get p() {
console.log("GET");
return this.r;
},
set q(v) {
console.log("SET", v);
},
r: 42,
},
r: null,
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
...{
get p() {
console.log("GET");
return this.r;
},
set q(v) {
console.log("SET", v);
},
r: 42,
},
r: null,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"GET",
"p 42",
"q undefined",
"r null",
]
node_version: ">=8"
}
object_key_order_1: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
...{},
a: 1,
b: 2,
a: 3,
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
a: (1, 3),
b: 2,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"a 3",
"b 2",
]
node_version: ">=8 <=10"
}
object_key_order_2: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
a: 1,
...{},
b: 2,
a: 3,
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
a: (1, 3),
b: 2,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"a 3",
"b 2",
]
node_version: ">=8"
}
object_key_order_3: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
a: 1,
b: 2,
...{},
a: 3,
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
a: (1, 3),
b: 2,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"a 3",
"b 2",
]
node_version: ">=8"
}
object_key_order_4: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
a: 1,
b: 2,
a: 3,
...{},
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
a: (1, 3),
b: 2,
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"a 3",
"b 2",
]
node_version: ">=8"
}
object_spread_array: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
...[ "foo", "bar" ],
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
...[ "foo", "bar" ],
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"0 foo",
"1 bar",
]
node_version: ">=8"
}
object_spread_string: {
options = {
objects: true,
spread: true,
}
input: {
var o = {
..."foo",
};
for (var k in o)
console.log(k, o[k]);
}
expect: {
var o = {
..."foo",
};
for (var k in o)
console.log(k, o[k]);
}
expect_stdout: [
"0 f",
"1 o",
"2 o",
]
node_version: ">=8"
}
unused_var_side_effects: {
options = {
unused: true,
}
input: {
(function f(a) {
var b = {
...a,
};
})({
get p() {
console.log("PASS");
},
});
}
expect: {
(function(a) {
({
...a,
});
})({
get p() {
console.log("PASS");
},
});
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4329: {
options = {
objects: true,
spread: true,
}
input: {
console.log({
...{
get 0() {
return "FAIL";
},
...{
0: "PASS",
},
},
}[0]);
}
expect: {
console.log({
...{
get 0() {
return "FAIL";
},
[0]: "PASS",
},
}[0]);
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4331: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var a = "PASS", b;
console,
b = a;
(function() {
a++;
})(...a);
console.log(b);
}
expect: {
var a = "PASS", b;
console;
(function() {
a++;
})(...b = a);
console.log(b);
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4342: {
options = {
side_effects: true,
}
input: {
try {
new function() {}(...42);
} catch (e) {
console.log("PASS");
}
}
expect: {
try {
[ ...42 ];
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_4345: {
options = {
objects: true,
spread: true,
}
input: {
console.log({
...{
get 42() {
return "FAIL";
},
...{},
42: "PASS",
},
}[42]);
}
expect: {
console.log({
...{
get 42() {
return "FAIL";
},
[42]: "PASS",
},
}[42]);
}
expect_stdout: "PASS"
node_version: ">=8"
}
issue_4361: {
options = {
reduce_vars: true,
unused: true,
}
input: {
console.log(function() {
var a = console.log("foo");
console;
var b = {
...a,
};
}());
}
expect: {
console.log(function() {
var a = console.log("foo");
console;
({
...a,
});
}());
}
expect_stdout: [
"foo",
"undefined",
]
node_version: ">=8"
}
issue_4363: {
options = {
objects: true,
spread: true,
}
input: {
({
...{
set [console.log("PASS")](v) {},
},
});
}
expect: {
({
[console.log("PASS")]: void 0,
});
}
expect_stdout: "PASS"
node_version: ">=8"
}

65
test/mocha/async.js Normal file
View File

@@ -0,0 +1,65 @@
var assert = require("assert");
var UglifyJS = require("../node");
describe("async", function() {
it("Should reject `await` as symbol name within async functions only", function() {
[
"function await() {}",
"function(await) {}",
"function() { await; }",
"function() { await:{} }",
"function() { var await; }",
"function() { function await() {} }",
"function() { try {} catch (await) {} }",
].forEach(function(code) {
var ast = UglifyJS.parse("(" + code + ")();");
assert.strictEqual(ast.TYPE, "Toplevel");
assert.strictEqual(ast.body.length, 1);
assert.strictEqual(ast.body[0].TYPE, "SimpleStatement");
assert.strictEqual(ast.body[0].body.TYPE, "Call");
assert.strictEqual(ast.body[0].body.expression.TYPE, "Function");
assert.throws(function() {
UglifyJS.parse("(async " + code + ")();");
}, function(e) {
return e instanceof UglifyJS.JS_Parse_Error;
}, code);
});
});
it("Should reject `await` expression outside of async functions", function() {
[
"await 42;",
"function f() { await 42; }",
"async function f() { function g() { await 42; } }",
].forEach(function(code) {
assert.throws(function() {
UglifyJS.parse(code);
}, function(e) {
return e instanceof UglifyJS.JS_Parse_Error;
}, code);
});
});
it("Should reject `await` expression directly on computed key of function argument", function() {
[
"function f({ [await 42]: a }) {}",
"async function f({ [await 42]: a }) {}",
].forEach(function(code) {
assert.throws(function() {
UglifyJS.parse(code);
}, function(e) {
return e instanceof UglifyJS.JS_Parse_Error;
}, code);
});
});
it("Should accept `await` expression nested within computed key of function argument", function() {
[
"function f({ [async function() { await 42; }()]: a }) {}",
"async function f({ [async function() { await 42; }()]: a }) {}",
].forEach(function(code) {
var ast = UglifyJS.parse(code);
assert.strictEqual(ast.TYPE, "Toplevel");
assert.strictEqual(ast.body.length, 1);
assert.strictEqual(ast.body[0].argnames.length, 1);
assert.strictEqual(ast.body[0].argnames[0].TYPE, "DestructuredObject");
});
});
});

View File

@@ -18,9 +18,18 @@ var sandbox = require("./sandbox");
Error.stackTraceLimit = Infinity;
module.exports = function reduce_test(testcase, minify_options, reduce_options) {
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string();
minify_options = minify_options || {};
reduce_options = reduce_options || {};
var print_options = {};
[
"ie8",
"v8",
"webkit",
].forEach(function(name) {
var value = minify_options[name] || minify_options.output && minify_options.output[name];
if (value) print_options[name] = value;
});
if (testcase instanceof U.AST_Node) testcase = testcase.print_to_string(print_options);
var max_iterations = reduce_options.max_iterations || 1000;
var max_timeout = reduce_options.max_timeout || 10000;
var warnings = [];
@@ -135,7 +144,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
if (expr && !(expr instanceof U.AST_Hole)) {
node.start._permute++;
CHANGED = true;
return expr;
return expr instanceof U.AST_Spread ? expr.expression : expr;
}
}
else if (node instanceof U.AST_Binary) {
@@ -164,7 +173,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
][ ((node.start._permute += step) * steps | 0) % 3 ];
if (expr) {
CHANGED = true;
return expr;
return expr instanceof U.AST_Spread ? expr.expression : expr;
}
if (node.expression instanceof U.AST_Function) {
// hoist and return expressions from the IIFE function expression
@@ -381,9 +390,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
if (in_list) {
// special case to drop object properties and switch branches
if (parent instanceof U.AST_Object
|| parent instanceof U.AST_Switch && parent.expression != node) {
// drop switch branches
if (parent instanceof U.AST_Switch && parent.expression != node) {
node.start._permute++;
CHANGED = true;
return List.skip;
@@ -404,7 +412,9 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
// skip element/property from (destructured) array/object
if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof AST_Object) {
if (parent instanceof U.AST_Array
|| parent instanceof U.AST_Destructured
|| parent instanceof U.AST_Object) {
node.start._permute++;
CHANGED = true;
return List.skip;
@@ -458,7 +468,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
return node;
}
}));
var code = testcase_ast.print_to_string();
var code = testcase_ast.print_to_string(print_options);
var diff = test_for_diff(code, minify_options, result_cache, max_timeout);
if (diff && !diff.timed_out && !diff.error) {
testcase = code;
@@ -482,7 +492,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
var code_ast = testcase_ast.clone(true).transform(tt);
if (!CHANGED) break;
try {
var code = code_ast.print_to_string();
var code = code_ast.print_to_string(print_options);
} catch (ex) {
// AST is not well formed.
// no harm done - just log the error, ignore latest change and continue iterating.
@@ -524,11 +534,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
var beautified = U.minify(testcase, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
comments: true,
},
output: function() {
var options = JSON.parse(JSON.stringify(print_options));
options.beautify = true;
options.braces = true;
options.comments = true;
return options;
}(),
});
testcase = {
code: testcase,
@@ -617,7 +629,7 @@ function is_timed_out(result) {
}
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_Function);
return node instanceof U.AST_Statement && !(node instanceof U.AST_AsyncFunction || node instanceof U.AST_Function);
}
function merge_sequence(array, node) {

View File

@@ -26,17 +26,27 @@ var setupContext = new vm.Script([
]).join("\n"));
function createContext() {
var ctx = vm.createContext(Object.defineProperty({}, "console", { value: { log: log } }));
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":
case "function":
return arg.toString();
case "object":
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);

View File

@@ -126,6 +126,22 @@ for (var i = 2; i < process.argv.length; ++i) {
}
}
var SUPPORT = function(matrix) {
for (var name in matrix) {
matrix[name] = typeof sandbox.run_code(matrix[name]) == "string";
}
return matrix;
}({
async: "async function f(){}",
catch_omit_var: "try {} catch {}",
computed_key: "({[0]: 0});",
destructuring: "[] = [];",
let: "let a;",
spread: "[...[]];",
spread_object: "({...0});",
trailing_comma: "function f(a,) {}",
});
var VALUES = [
'"a"',
'"b"',
@@ -298,6 +314,8 @@ var VAR_NAMES = [
"arguments",
"Math",
"parseInt",
"async",
"await",
];
var INITIAL_NAMES_LEN = VAR_NAMES.length;
@@ -314,8 +332,10 @@ var TYPEOF_OUTCOMES = [
"crap",
];
var avoid_vars = [];
var block_vars = [];
var unique_vars = [];
var async = false;
var loops = 0;
var funcs = 0;
var called = Object.create(null);
@@ -334,6 +354,7 @@ function createTopLevelCode() {
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
block_vars.length = 0;
unique_vars.length = 0;
async = false;
loops = 0;
funcs = 0;
called = Object.create(null);
@@ -357,6 +378,10 @@ function createFunctions(n, recurmax, allowDefun, canThrow, stmtDepth) {
return s;
}
function addTrailingComma(list) {
return SUPPORT.trailing_comma && list && rng(20) == 0 ? list + "," : list;
}
function createParams(noDuplicate) {
var len = unique_vars.length;
var params = [];
@@ -366,18 +391,33 @@ function createParams(noDuplicate) {
params.push(name);
}
unique_vars.length = len;
return params.join(", ");
return addTrailingComma(params.join(", "));
}
function createArgs(recurmax, stmtDepth, canThrow) {
recurmax--;
var args = [];
for (var n = rng(4); --n >= 0;) {
args.push(rng(2) ? createValue() : createExpression(recurmax - 1, COMMA_OK, stmtDepth, canThrow));
for (var n = rng(4); --n >= 0;) switch (SUPPORT.spread ? rng(50) : 3) {
case 0:
case 1:
var name = getVarName();
if (canThrow && rng(8) === 0) {
args.push("..." + name);
} else {
args.push('...("" + ' + name + ")");
}
break;
case 2:
args.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
break;
default:
args.push(rng(2) ? createValue() : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
break;
}
return args.join(", ");
return addTrailingComma(args.join(", "));
}
function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, maybe, dontStore) {
function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, was_async) {
var avoid = [];
var len = unique_vars.length;
var pairs = createPairs(recurmax);
@@ -387,22 +427,21 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames,
function createAssignmentValue(recurmax) {
var current = VAR_NAMES;
VAR_NAMES = (varNames || VAR_NAMES).slice();
var save_async = async;
if (was_async != null) async = was_async;
var value = varNames && rng(2) ? createValue() : createExpression(recurmax, noComma, stmtDepth, canThrow);
async = save_async;
VAR_NAMES = current;
return value;
}
function createKey(recurmax, keys) {
var save = VAR_NAMES;
VAR_NAMES = VAR_NAMES.filter(function(name) {
return avoid.indexOf(name) < 0;
});
var len = VAR_NAMES.length;
addAvoidVars(avoid);
var key;
do {
key = createObjectKey(recurmax, stmtDepth, canThrow);
} while (keys.indexOf(key) >= 0);
VAR_NAMES = save.concat(VAR_NAMES.slice(len));
removeAvoidVars(avoid);
return key;
}
@@ -417,7 +456,7 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames,
}
if (i < m) {
unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
var name = createVarName(maybe, dontStore);
var name = createVarName(MANDATORY);
unique_vars.length -= 6;
avoid.push(name);
unique_vars.push(name);
@@ -444,7 +483,14 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames,
while (!rng(10)) {
var index = rng(pairs.names.length + 1);
pairs.names.splice(index, 0, "");
pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
if (index < pairs.values.length) {
pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
} else switch (rng(5)) {
case 0:
pairs.values[index] = createAssignmentValue(recurmax);
case 1:
pairs.values.length = index + 1;
}
}
names.unshift("[ " + pairs.names.join(", ") + " ]");
values.unshift("[ " + pairs.values.join(", ") + " ]");
@@ -466,19 +512,22 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames,
keys[index] = key;
}
});
names.unshift("{ " + pairs.names.map(function(name, index) {
names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) {
var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys);
return key ? key + ": " + name : name;
}).join(", ") + " }");
values.unshift("{ " + pairs.values.map(function(value, index) {
}).join(", ")) + " }");
var save_async = async;
if (was_async != null) async = was_async;
values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) {
var key = index in keys ? keys[index] : createKey(recurmax, keys);
return key + ": " + value;
}).join(", ") + " }");
}).join(", ")) + " }");
async = save_async;
}
break;
default:
unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
var name = createVarName(maybe, dontStore);
var name = createVarName(MANDATORY);
unique_vars.length -= 6;
avoid.push(name);
unique_vars.push(name);
@@ -502,15 +551,17 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
while (!rng(block_vars.length > block_len ? 10 : 100)) {
var name = createVarName(MANDATORY, DONT_STORE);
if (rng(2)) {
consts.push(name);
} else {
if (SUPPORT.let && rng(2)) {
lets.push(name);
} else {
consts.push(name);
}
block_vars.push(name);
}
unique_vars.length -= 6;
fn(function() {
addAvoidVars(consts);
addAvoidVars(lets);
if (rng(2)) {
return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n";
} else {
@@ -522,13 +573,8 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
function createDefinitions(type, names) {
if (!names.length) return "";
var save = VAR_NAMES;
VAR_NAMES = VAR_NAMES.filter(function(name) {
return names.indexOf(name) < 0;
});
var len = VAR_NAMES.length;
var s = type + " ";
switch (rng(10)) {
switch (SUPPORT.destructuring ? rng(10) : 2) {
case 0:
while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
s += "[ " + names.join(", ") + " ] = [ " + names.map(function() {
@@ -547,28 +593,34 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
}).join(", ") + "};";
break;
default:
s += names.map(function(name, i) {
s += names.map(function(name) {
if (type == "let" && !rng(10)) {
VAR_NAMES.push(name);
removeAvoidVars([ name ]);
return name;
}
var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
VAR_NAMES.push(name);
removeAvoidVars([ name ]);
return name + " = " + value;
}).join(", ") + ";";
break;
}
VAR_NAMES = save.concat(VAR_NAMES.slice(len));
removeAvoidVars(names);
return s;
}
}
function makeFunction(name) {
return (async ? "async function " : "function ") + name;
}
function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
if (--recurmax < 0) { return ";"; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var s = [];
var name, args;
var varNames = VAR_NAMES.slice();
var save_async = async;
async = SUPPORT.async && rng(50) == 0;
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
if (allowDefun || rng(5) > 0) {
name = "f" + funcs++;
@@ -578,15 +630,15 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
unique_vars.length -= 3;
}
var params;
if ((!allowDefun || !(name in called)) && rng(2)) {
if (SUPPORT.destructuring && (!allowDefun || !(name in called)) && rng(2)) {
called[name] = false;
var pairs = createAssignmentPairs(recurmax, COMMA_OK, stmtDepth, canThrow, varNames, MANDATORY);
params = pairs.names.join(", ");
args = pairs.values.join(", ");
var pairs = createAssignmentPairs(recurmax, COMMA_OK, stmtDepth, canThrow, varNames, save_async);
params = addTrailingComma(pairs.names.join(", "));
args = addTrailingComma(pairs.values.join(", "));
} else {
params = createParams();
}
s.push("function " + name + "(" + params + "){", strictMode());
s.push(makeFunction(name) + "(" + params + "){", strictMode());
s.push(defns());
if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess.
@@ -598,6 +650,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
s.push("}", "");
s = filterDirective(s).join("\n");
});
async = save_async;
VAR_NAMES = varNames;
if (!allowDefun) {
@@ -688,17 +741,17 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return "{var brake" + loop + " = 5; " + label.target + "do {" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "} while ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") && --brake" + loop + " > 0);}";
return "{var brake" + loop + " = 5; " + label.target + "do {" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "} while (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " && --brake" + loop + " > 0);}";
case STMT_WHILE:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return "{var brake" + loop + " = 5; " + label.target + "while ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") && --brake" + loop + " > 0)" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
return "{var brake" + loop + " = 5; " + label.target + "while (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " && --brake" + loop + " > 0)" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
case STMT_FOR_LOOP:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
canContinue = label.continue || enableLoopControl(canContinue, CAN_CONTINUE);
return label.target + "for (var brake" + loop + " = 5; (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") && brake" + loop + " > 0; --brake" + loop + ")" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
return label.target + "for (var brake" + loop + " = 5; " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " && brake" + loop + " > 0; --brake" + loop + ")" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
case STMT_FOR_IN:
var label = createLabel(canBreak, canContinue);
canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK);
@@ -707,8 +760,8 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return [
"{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
label.target + " for (",
!/^key/.test(key) ? rng(10) ? "" : "var " : rng(10) ? "var " : rng(2) ? "let " : "const ",
rng(10) ? key : rng(5) ? "[ " + key + " ]" : "{ length: " + key + " }",
!/^key/.test(key) ? rng(10) ? "" : "var " : !SUPPORT.let || rng(10) ? "var " : rng(2) ? "let " : "const ",
!SUPPORT.destructuring || rng(10) ? key : rng(5) ? "[ " + key + " ]" : "{ length: " + key + " }",
" in expr" + loop + ") {",
rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth),
@@ -723,8 +776,8 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
// note: default does not _need_ to be last
return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
case STMT_VAR:
if (!rng(20)) {
var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow, null, MANDATORY);
if (SUPPORT.destructuring && rng(20) == 0) {
var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow);
return "var " + pairs.names.map(function(name, index) {
return index in pairs.values ? name + " = " + pairs.values[index] : name;
}).join(", ") + ";";
@@ -785,10 +838,14 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
// we have to do go through some trouble here to prevent leaking it
var nameLenBefore = VAR_NAMES.length;
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
var catchName = createVarName(MANDATORY);
if (SUPPORT.catch_omit_var && rng(20) == 0) {
s += " catch { ";
} else {
var catchName = createVarName(MANDATORY);
if (!catch_redef) unique_vars.push(catchName);
s += " catch (" + catchName + ") { ";
}
var freshCatchName = VAR_NAMES.length !== nameLenBefore;
if (!catch_redef) unique_vars.push(catchName);
s += " catch (" + catchName + ") { ";
s += defns() + "\n";
s += _createStatements(3, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth);
s += " }";
@@ -844,7 +901,8 @@ function createExpression(recurmax, noComma, stmtDepth, canThrow) {
case 2:
return "((c = c + 1) + (" + _createExpression(recurmax, noComma, stmtDepth, canThrow) + "))"; // c only gets incremented
default:
return "(" + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ")";
var expr = "(" + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ")";
return async && rng(50) == 0 ? "(await" + expr + ")" : expr;
}
}
@@ -871,7 +929,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
return getVarName();
case p++:
switch (rng(20)) {
switch (SUPPORT.destructuring ? rng(20) : 2) {
case 0:
return [
"[ ",
@@ -896,10 +954,12 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
return createExpression(recurmax, noComma, stmtDepth, canThrow) + "?" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + ":" + createExpression(recurmax, noComma, stmtDepth, canThrow);
return createExpression(recurmax, noComma, stmtDepth, canThrow) + " ? " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + " : " + createExpression(recurmax, noComma, stmtDepth, canThrow);
case p++:
case p++:
var nameLenBefore = VAR_NAMES.length;
var save_async = async;
async = SUPPORT.async && rng(50) == 0;
unique_vars.push("c");
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
unique_vars.pop();
@@ -907,7 +967,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
switch (rng(5)) {
case 0:
s.push(
"(function " + name + "(){",
"(" + makeFunction(name) + "(){",
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
rng(2) == 0 ? "})" : "})()"
@@ -915,7 +975,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
break;
case 1:
s.push(
"+function " + name + "(){",
"+" + makeFunction(name) + "(){",
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"}()"
@@ -923,7 +983,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
break;
case 2:
s.push(
"!function " + name + "(){",
"!" + makeFunction(name) + "(){",
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"}()"
@@ -931,17 +991,18 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
break;
case 3:
s.push(
"void function " + name + "(){",
"void " + makeFunction(name) + "(){",
strictMode(),
createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"}()"
);
break;
default:
async = false;
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
var instantiate = rng(4) ? "new " : "";
s.push(
instantiate + "function " + name + "(){",
instantiate + "function " + name + "(" + createParams() + "){",
strictMode(),
defns()
);
@@ -949,13 +1010,14 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
}
s.push(
_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
rng(2) == 0 ? "}" : "}()"
);
s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
});
async = save_async;
VAR_NAMES.length = nameLenBefore;
s.push(rng(2) == 0 ? "}" : "}(" + createArgs(recurmax, stmtDepth, canThrow) + ")");
break;
}
async = save_async;
VAR_NAMES.length = nameLenBefore;
return filterDirective(s).join("\n");
case p++:
@@ -992,14 +1054,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
return createUnarySafePrefix() + "(" + createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || a || 3).toString() ";
return " (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " || a || 3).toString() ";
case p++:
return " /[abc4]/.test(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
return " /[abc4]/.test((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " || b || 5).toString()) ";
case p++:
return " /[abc4]/g.exec(((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") || b || 5).toString()) ";
return " /[abc4]/g.exec((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " || b || 5).toString()) ";
case p++:
return " ((" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) +
") || " + rng(10) + ").toString()[" +
return " (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + " || " + rng(10) + ").toString()[" +
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "] ";
case p++:
return createArrayLiteral(recurmax, stmtDepth, canThrow);
@@ -1050,13 +1111,30 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
function createArrayLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var arr = "[";
for (var i = rng(6); --i >= 0;) {
var arr = [];
for (var i = rng(6); --i >= 0;) switch (SUPPORT.spread ? rng(50) : 3 + rng(47)) {
case 0:
case 1:
var name = getVarName();
if (canThrow && rng(8) === 0) {
arr.push("..." + name);
} else {
arr.push('...("" + ' + name + ")");
}
break;
case 2:
arr.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow));
break;
case 3:
case 4:
// in rare cases produce an array hole element
var element = rng(20) ? createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) : "";
arr += element + ", ";
arr.push("");
break;
default:
arr.push(createExpression(recurmax, COMMA_OK, stmtDepth, canThrow));
break;
}
return arr + "]";
return "[" + arr.join(", ") + "]";
}
var SAFE_KEYS = [
@@ -1090,14 +1168,17 @@ function getDotKey(assign) {
}
function createObjectKey(recurmax, stmtDepth, canThrow) {
return rng(10) ? KEYS[rng(KEYS.length)] : "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]";
if (SUPPORT.computed_key && rng(10) == 0) {
return "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]";
}
return KEYS[rng(KEYS.length)];
}
function createObjectFunction(recurmax, stmtDepth, canThrow) {
var namesLenBefore = VAR_NAMES.length;
var s;
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
switch (rng(3)) {
switch (rng(SUPPORT.computed_key ? 3 : 2)) {
case 0:
s = [
"get " + createObjectKey(recurmax, stmtDepth, canThrow) + "(){",
@@ -1141,15 +1222,23 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var obj = ["({"];
for (var i = rng(6); --i >= 0;) switch (rng(30)) {
var offset = SUPPORT.spread_object ? 0 : SUPPORT.computed_key ? 2 : 4;
for (var i = rng(6); --i >= 0;) switch (offset + rng(50 - offset)) {
case 0:
obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
obj.push("..." + getVarName() + ",");
break;
case 1:
obj.push("..." + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ",");
break;
case 2:
case 3:
obj.push(getVarName() + ",");
break;
case 4:
obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
break;
default:
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ": " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ",");
break;
}
obj.push("})");
@@ -1178,7 +1267,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case 3:
assignee = getVarName();
switch (rng(20)) {
switch (SUPPORT.destructuring ? rng(20) : 2) {
case 0:
expr = [
"([ ",
@@ -1215,7 +1304,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
case 4:
assignee = getVarName();
switch (rng(20)) {
switch (SUPPORT.destructuring ? rng(20) : 2) {
case 0:
expr = [
"([ ",
@@ -1300,10 +1389,25 @@ function createUnaryPostfix() {
return UNARY_POSTFIX[rng(UNARY_POSTFIX.length)];
}
function addAvoidVars(names) {
avoid_vars = avoid_vars.concat(names);
}
function removeAvoidVars(names) {
names.forEach(function(name) {
var index = avoid_vars.lastIndexOf(name);
if (index >= 0) avoid_vars.splice(index, 1);
});
}
function getVarName(noConst) {
// try to get a generated name reachable from current scope. default to just `a`
var name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)];
return !name || noConst && block_vars.indexOf(name) >= 0 ? "a" : name;
var name, tries = 10;
do {
if (--tries < 0) return "a";
name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)];
} while (!name || avoid_vars.indexOf(name) >= 0 || noConst && block_vars.indexOf(name) >= 0);
return name;
}
function createVarName(maybe, dontStore) {
@@ -1313,7 +1417,7 @@ function createVarName(maybe, dontStore) {
do {
name = VAR_NAMES[rng(VAR_NAMES.length)];
if (suffix) name += "_" + suffix;
} while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0);
} while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 || async && name == "await");
if (suffix && !dontStore) VAR_NAMES.push(name);
return name;
}
@@ -1341,25 +1445,35 @@ function errorln(msg) {
writeln(process.stderr, msg);
}
function try_beautify(code, toplevel, result, printfn) {
var beautified = UglifyJS.minify(code, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
},
});
function try_beautify(code, toplevel, result, printfn, options) {
var beautified = UglifyJS.minify(code, JSON.parse(beautify_options));
if (beautified.error) {
printfn("// !!! beautify failed !!!");
printfn(beautified.error);
} else if (sandbox.same_stdout(sandbox.run_code(beautified.code, toplevel), result)) {
beautified = null;
} else if (!sandbox.same_stdout(sandbox.run_code(beautified.code, toplevel), result)) {
beautified = null;
} else if (options) {
var uglified = UglifyJS.minify(beautified.code, JSON.parse(options));
var expected, actual;
if (typeof uglify_code != "string" || uglified.error) {
expected = uglify_code;
actual = uglified.error;
} else {
expected = uglify_result;
actual = sandbox.run_code(uglified.code, toplevel);
}
if (!sandbox.same_stdout(expected, actual)) {
beautified = null;
}
}
if (beautified) {
printfn("// (beautified)");
printfn(beautified.code);
return;
} else {
printfn("//");
printfn(code);
}
printfn("//");
printfn(code);
}
var default_options = UglifyJS.default_options();
@@ -1432,37 +1546,7 @@ function log(options) {
errorln("//=============================================================");
if (!ok) errorln("// !!!!!! Failed... round " + round);
errorln("// original code");
var beautified = UglifyJS.minify(original_code, {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
},
});
if (beautified.error) {
errorln("// !!! beautify failed !!!");
errorln(beautified.error);
errorln("//");
errorln(original_code);
} else {
var uglified = UglifyJS.minify(beautified.code, JSON.parse(options));
var expected, actual;
if (typeof uglify_code != "string" || uglified.error) {
expected = uglify_code;
actual = uglified.error;
} else {
expected = uglify_result;
actual = sandbox.run_code(uglified.code, toplevel);
}
if (sandbox.same_stdout(expected, actual)) {
errorln("// (beautified)");
errorln(beautified.code);
} else {
errorln("//");
errorln(original_code);
}
}
try_beautify(original_code, toplevel, original_result, errorln, options);
errorln();
errorln();
errorln("//-------------------------------------------------------------");
@@ -1582,6 +1666,9 @@ function patch_try_catch(orig, toplevel) {
} else if (result.name == "TypeError" && /'in'/.test(result.message)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("invalid `in`");' + orig.slice(index);
} else if (result.name == "TypeError" && /not iterable/.test(result.message)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("spread not iterable");' + orig.slice(index);
} else if (result.name == "RangeError" && result.message == "Maximum call stack size exceeded") {
index = result.ufuzz_try;
return orig.slice(0, index) + 'throw new Error("skipping infinite recursion");' + orig.slice(index);
@@ -1591,13 +1678,23 @@ function patch_try_catch(orig, toplevel) {
}
}
var beautify_options = {
compress: false,
mangle: false,
output: {
beautify: true,
braces: true,
},
};
var minify_options = require("./options.json");
if (typeof sandbox.run_code("console.log([ 1 ], {} = 2);") != "string") {
if (SUPPORT.destructuring && typeof sandbox.run_code("console.log([ 1 ], {} = 2);") != "string") {
beautify_options.output.v8 = true;
minify_options.forEach(function(o) {
if (!("output" in o)) o.output = {};
o.output.v8 = true;
});
}
beautify_options = JSON.stringify(beautify_options);
minify_options = minify_options.map(JSON.stringify);
var original_code, original_result, errored;
var uglify_code, uglify_result, ok;
@@ -1647,6 +1744,7 @@ for (var round = 1; round <= num_iterations; round++) {
ok = sandbox.same_stdout(orig_result[toplevel ? 3 : 2], uglify_result);
}
// ignore difference in error message caused by `in`
// ignore difference in error message caused by spread syntax
// ignore difference in depth of termination caused by infinite recursion
if (!ok) {
var orig_skipped = patch_try_catch(original_code, toplevel);