Compare commits

...

25 Commits

Author SHA1 Message Date
Alex Lam S.L
9f67866147 v3.12.0 2020-11-23 01:10:39 +08:00
Alex Lam S.L
645d5a348b workaround Safari quirks (#4314)
fixes #1753
2020-11-21 10:30:46 +08:00
Alex Lam S.L
cf120c7cea fix corner case in merge_vars & reduce_vars (#4313)
fixes #4312
2020-11-21 08:57:59 +08:00
Alex Lam S.L
8d30902ba9 fix corner case in mangle (#4311) 2020-11-21 08:05:40 +08:00
Alex Lam S.L
02459cddf9 gate galio workaround (#4310) 2020-11-21 03:37:33 +08:00
Alex Lam S.L
1b579779be fix corner case in collapse_vars (#4309)
fixes #4308
2020-11-20 06:23:37 +08:00
Alex Lam S.L
b18b70f63b fix corner case in hoist_props (#4307) 2020-11-20 00:02:25 +08:00
Alex Lam S.L
641406d491 fix corner cases in reduce_vars & unused (#4306) 2020-11-19 11:25:36 +08:00
Alex Lam S.L
134ef0b1eb fix corner case in dead_code (#4304) 2020-11-19 08:34:55 +08:00
Alex Lam S.L
db87dcf13e enhance varify (#4303) 2020-11-19 07:58:33 +08:00
Alex Lam S.L
aecbabc587 fix corner case in merge_vars (#4302)
fixes #4301
2020-11-19 05:44:47 +08:00
Alex Lam S.L
fd6544b340 fix corner case reduce_vars (#4300)
fixes #4297
2020-11-19 01:11:28 +08:00
Alex Lam S.L
f6a83f7944 fix corner case in merge_vars (#4299)
fixes #4298
2020-11-18 23:43:55 +08:00
Alex Lam S.L
35283e5dd1 enhance arguments (#4296) 2020-11-18 11:39:32 +08:00
Alex Lam S.L
7a51c17ff0 fix corner case in merge_vars (#4295)
fixes #4294
2020-11-18 09:32:53 +08:00
Alex Lam S.L
aff842f2f9 fix corner case in arguments (#4293)
fixes #4291
2020-11-18 08:54:58 +08:00
Alex Lam S.L
0bedd031da fix corner cases in collapse_vars, unused & varify (#4292)
fixes #4290
2020-11-18 08:22:54 +08:00
Alex Lam S.L
caa92aea5d fix corner case in merge_vars (#4289)
fixes #4288
2020-11-18 04:03:20 +08:00
Alex Lam S.L
383163afa6 fix corner case in collapse_vars (#4287)
fixes #4286
2020-11-17 18:03:31 +08:00
Alex Lam S.L
8a83c8dd46 fix corner cases in collapse_vars & dead_code (#4285)
fixes #4284
2020-11-17 16:23:50 +08:00
Alex Lam S.L
2a612fd472 fix corner case in reduce_vars (#4283)
fixes #4282
2020-11-17 14:43:04 +08:00
Alex Lam S.L
b9798a01a8 fix corner case in reduce_vars (#4281)
fixes #4280
2020-11-17 12:59:44 +08:00
Alex Lam S.L
6dbacb5e3f enhance varify (#4279) 2020-11-17 12:35:00 +08:00
Alex Lam S.L
e5f80afc53 support destructured literals (#4278) 2020-11-17 08:01:24 +08:00
Alex Lam S.L
42e34c870a fix corner case in unused (#4277)
fixes #4276
2020-11-17 02:06:00 +08:00
21 changed files with 3729 additions and 426 deletions

View File

@@ -135,6 +135,10 @@ a double dash to prevent input files being used as option arguments:
--toplevel Compress and/or mangle variables in top level scope.
--verbose Print diagnostic messages.
--warn Print warning messages.
--webkit Support non-standard Safari/Webkit.
Equivalent to setting `webkit: true` in `minify()`
for `mangle` and `output` options.
By default UglifyJS will not try to be Safari-proof.
--wrap <name> Embed everything in a big function, making the
“exports” and “global” variables available. You
need to pass an argument to this option to
@@ -519,6 +523,9 @@ if (result.error) throw result.error;
- `warnings` (default `false`) — pass `true` to return compressor warnings
in `result.warnings`. Use the value `"verbose"` for more detailed warnings.
- `webkit` (default `false`) -- enable workarounds for Safari/WebKit bugs.
PhantomJS users should set this option to `true`.
## Minify options structure
```javascript
@@ -868,6 +875,8 @@ can pass additional arguments that control the code output:
}
```
- `galio` (default `false`) -- enable workarounds for ANT Galio bugs
- `indent_level` (default `4`)
- `indent_start` (default `0`) -- prefix all lines by that many spaces
@@ -906,8 +915,7 @@ can pass additional arguments that control the code output:
- `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
- `webkit` (default `false`) -- enable workarounds for WebKit bugs.
PhantomJS users should set this option to `true`.
- `v8` (default `false`) -- enable workarounds for Chrome & Node.js bugs
- `width` (default `80`) -- only takes effect when beautification is on, this
specifies an (orientative) line width that the beautifier will try to
@@ -1138,7 +1146,7 @@ To enable fast minify mode with the API use:
UglifyJS.minify(code, { compress: false, mangle: true });
```
#### Source maps and debugging
### Source maps and debugging
Various `compress` transforms that simplify, rearrange, inline and remove code
are known to have an adverse effect on debugging with source maps. This is
@@ -1150,6 +1158,10 @@ disable the Uglify `compress` option and just use `mangle`.
To allow for better optimizations, the compiler makes various assumptions:
- The code does not rely on preserving its runtime performance characteristics.
Typically uglified code will run faster due to less instructions and easier
inlining, but may be slower on rare occasions for a specific platform, e.g.
see [`reduce_funcs`](#compress-options).
- `.toString()` and `.valueOf()` don't have side effects, and for built-in
objects they have not been overridden.
- `undefined`, `NaN` and `Infinity` have not been externally redefined.
@@ -1177,3 +1189,7 @@ To allow for better optimizations, the compiler makes various assumptions:
top.B = "PASS";
console.log(B);
```
- Use of `arguments` alongside destructuring as function parameters, e.g.
`function({}, arguments) {}` will result in `SyntaxError` in earlier versions
of Chrome and Node.js - UglifyJS may modify the input which in turn may
suppress those errors.

View File

@@ -111,6 +111,7 @@ function process_option(name, no_value) {
" --validate Perform validation during AST manipulations.",
" --verbose Print diagnostic messages.",
" --warn Print warning messages.",
" --webkit Support non-standard Safari/Webkit.",
" --wrap <name> Embed everything as a function with “exports” corresponding to “name” globally.",
" --reduce-test Reduce a standalone test case (assumes cloned repository).",
].join("\n"));
@@ -142,6 +143,7 @@ function process_option(name, no_value) {
case "timings":
case "toplevel":
case "validate":
case "webkit":
options[name] = true;
break;
case "keep-fnames":

View File

@@ -414,8 +414,12 @@ var AST_ForIn = DEFNODE("ForIn", "init object", {
_validate: function() {
if (this.init instanceof AST_Definitions) {
if (this.init.definitions.length != 1) throw new Error("init must have single declaration");
} else if (!(this.init instanceof AST_PropAccess || this.init instanceof AST_SymbolRef)) {
throw new Error("init must be assignable");
} else {
validate_destructured(this.init, function(node) {
if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
throw new Error("init must be assignable: " + node.TYPE);
}
});
}
must_be_expression(this, "object");
},
@@ -496,12 +500,24 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}, AST_Scope);
var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function",
argnames: "[AST_SymbolFunarg*] array of function arguments",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array"
argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
},
each_argname: function(visit) {
var tw = new TreeWalker(function(node) {
if (node instanceof AST_DestructuredKeyVal) {
node.value.walk(tw);
return true;
}
if (node instanceof AST_SymbolFunarg) visit(node);
});
this.argnames.forEach(function(argname) {
argname.walk(tw);
});
},
walk: function(visitor) {
var node = this;
@@ -515,7 +531,9 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
},
_validate: function() {
this.argnames.forEach(function(node) {
if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
validate_destructured(node, function(node) {
if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]");
});
});
},
}, AST_Scope);
@@ -748,8 +766,10 @@ var AST_Const = DEFNODE("Const", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
if (!(node.name instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
must_be_expression(node, "value");
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
});
if (node.value != null) must_be_expression(node, "value");
});
},
}, AST_Definitions);
@@ -759,7 +779,9 @@ var AST_Let = DEFNODE("Let", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
if (!(node.name instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolLet)) throw new Error("name must be AST_SymbolLet");
});
if (node.value != null) must_be_expression(node, "value");
});
},
@@ -770,7 +792,9 @@ var AST_Var = DEFNODE("Var", null, {
_validate: function() {
this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]");
if (!(node.name instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
validate_destructured(node.name, function(node) {
if (!(node instanceof AST_SymbolVar)) throw new Error("name must be AST_SymbolVar");
});
if (node.value != null) must_be_expression(node, "value");
});
},
@@ -969,6 +993,14 @@ var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`",
_validate: function() {
if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="');
if (this.left instanceof AST_Destructured) {
if (this.operator != "=") throw new Error("invalid destructuring operator: " + this.operator);
validate_destructured(this.left, function(node) {
if (!(node instanceof AST_PropAccess || node instanceof AST_SymbolRef)) {
throw new Error("left must be assignable: " + node.TYPE);
}
});
}
},
}, AST_Binary);
@@ -992,6 +1024,77 @@ var AST_Array = DEFNODE("Array", "elements", {
},
});
var AST_Destructured = DEFNODE("Destructured", null, {
$documentation: "Base class for destructured literal",
});
function validate_destructured(node, check) {
if (node instanceof AST_DestructuredArray) return node.elements.forEach(function(node) {
if (!(node instanceof AST_Hole)) validate_destructured(node, check);
});
if (node instanceof AST_DestructuredObject) return node.properties.forEach(function(prop) {
validate_destructured(prop.value, check);
});
check(node);
}
var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
$documentation: "A destructured array literal",
$propdoc: {
elements: "[AST_Node*] array of elements",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
node.elements.forEach(function(element) {
element.walk(visitor);
});
});
},
}, AST_Destructured);
var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
$documentation: "A key: value destructured property",
$propdoc: {
key: "[string|AST_Node] property name. For computed property this is an AST_Node.",
value: "[AST_Node] property value",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
if (node.key instanceof AST_Node) node.key.walk(visitor);
node.value.walk(visitor);
});
},
_validate: function() {
if (typeof this.key != "string") {
if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
must_be_expression(this, "key");
}
must_be_expression(this, "value");
},
});
var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
$documentation: "A destructured object literal",
$propdoc: {
properties: "[AST_DestructuredKeyVal*] array of properties",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
node.properties.forEach(function(prop) {
prop.walk(visitor);
});
});
},
_validate: function() {
this.properties.forEach(function(node) {
if (!(node instanceof AST_DestructuredKeyVal)) throw new Error("properties must be AST_DestructuredKeyVal[]");
});
},
}, AST_Destructured);
var AST_Object = DEFNODE("Object", "properties", {
$documentation: "An object literal",
$propdoc: {

File diff suppressed because it is too large Load Diff

View File

@@ -89,6 +89,7 @@ function minify(files, options) {
toplevel: false,
validate: false,
warnings: false,
webkit: false,
wrap: false,
}, true);
if (options.validate) AST_Node.enable_validation();
@@ -101,6 +102,7 @@ function minify(files, options) {
set_shorthand("ie8", options, [ "compress", "mangle", "output" ]);
set_shorthand("keep_fnames", options, [ "compress", "mangle" ]);
set_shorthand("toplevel", options, [ "compress", "mangle" ]);
set_shorthand("webkit", options, [ "mangle", "output" ]);
var quoted_props;
if (options.mangle) {
options.mangle = defaults(options.mangle, {
@@ -111,6 +113,7 @@ function minify(files, options) {
properties: false,
reserved: [],
toplevel: false,
webkit: false,
}, true);
if (options.mangle.properties) {
if (typeof options.mangle.properties != "object") {

View File

@@ -56,6 +56,7 @@ function OutputStream(options) {
beautify : false,
braces : false,
comments : false,
galio : false,
ie8 : false,
indent_level : 4,
indent_start : 0,
@@ -69,6 +70,7 @@ function OutputStream(options) {
semicolons : true,
shebang : true,
source_map : null,
v8 : false,
webkit : false,
width : 80,
wrap_iife : false,
@@ -499,11 +501,11 @@ function OutputStream(options) {
}
}
if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n");
print("//" + c.value.replace(/[@#]__PURE__/g, " ") + "\n");
indent();
last_nlb = true;
} else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
last_nlb = false;
}
});
@@ -557,10 +559,10 @@ function OutputStream(options) {
space();
}
if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' '));
print("//" + c.value.replace(/[@#]__PURE__/g, " "));
need_newline_indented = true;
} else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/");
print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
need_space = true;
}
});
@@ -610,7 +612,7 @@ function OutputStream(options) {
},
parent : function(n) {
return stack[stack.length - 2 - (n || 0)];
}
},
};
}
@@ -652,13 +654,7 @@ function OutputStream(options) {
/* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) {
if (Array.isArray(nodetype)) {
nodetype.forEach(function(nodetype) {
PARENS(nodetype, func);
});
} else {
nodetype.DEFMETHOD("needs_parens", func);
}
nodetype.DEFMETHOD("needs_parens", func);
}
PARENS(AST_Node, return_false);
@@ -667,11 +663,11 @@ function OutputStream(options) {
// the first token to appear in a statement.
PARENS(AST_Function, function(output) {
if (!output.has_parens() && first_in_statement(output)) return true;
if (output.option('webkit')) {
if (output.option("webkit")) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) return true;
}
if (output.option('wrap_iife')) {
if (output.option("wrap_iife")) {
var p = output.parent();
if (p instanceof AST_Call && p.expression === this) return true;
}
@@ -679,9 +675,10 @@ function OutputStream(options) {
// same goes for an object literal, because otherwise it would be
// interpreted as a block of code.
PARENS(AST_Object, function(output) {
function needs_parens_obj(output) {
return !output.has_parens() && first_in_statement(output);
});
}
PARENS(AST_Object, needs_parens_obj);
PARENS(AST_Unary, function(output) {
var p = output.parent();
@@ -701,6 +698,7 @@ function OutputStream(options) {
|| p instanceof AST_Conditional
// { [(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
|| p instanceof AST_PropAccess && p.expression === this
@@ -747,7 +745,7 @@ function OutputStream(options) {
var p = output.parent();
if (p instanceof AST_New) return p.expression === this;
// https://bugs.webkit.org/show_bug.cgi?id=123506
if (output.option('webkit')) {
if (output.option("webkit")) {
var g = output.parent(1);
return this.expression instanceof AST_Function
&& p instanceof AST_PropAccess
@@ -770,24 +768,36 @@ function OutputStream(options) {
var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) {
var value = this.value;
// https://github.com/mishoo/UglifyJS/issues/115
// https://github.com/mishoo/UglifyJS/pull/1009
return value < 0 || /^0/.test(make_num(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));
}
});
PARENS([ AST_Assign, AST_Conditional ], function(output) {
function needs_parens_assign_cond(self, output) {
var p = output.parent();
// 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)()
if (p instanceof AST_Call) return p.expression === this;
if (p instanceof AST_Call) return p.expression === self;
// (a = foo) ? bar : baz
if (p instanceof AST_Conditional) return p.condition === this;
if (p instanceof AST_Conditional) return p.condition === self;
// (a = foo)["prop"] —or— (a = foo).prop
if (p instanceof AST_PropAccess) return p.expression === this;
if (p instanceof AST_PropAccess) return p.expression === self;
// !(a = false) → true
if (p instanceof AST_Unary) return true;
}
PARENS(AST_Assign, function(output) {
if (needs_parens_assign_cond(this, output)) return true;
// 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);
});
PARENS(AST_Conditional, function(output) {
return needs_parens_assign_cond(this, output);
});
/* -----[ PRINTERS ]----- */
@@ -1274,6 +1284,38 @@ function OutputStream(options) {
output.space();
} : noop);
});
DEFPRINT(AST_DestructuredArray, function(output) {
var a = this.elements, len = a.length;
output.with_square(len > 0 ? function() {
output.space();
a.forEach(function(exp, i) {
if (i) output.comma();
exp.print(output);
// If the final element is a hole, we need to make sure it
// doesn't look like a trailing comma, by inserting an actual
// trailing comma.
if (i === len - 1 && exp instanceof AST_Hole)
output.comma();
});
output.space();
} : noop);
});
DEFPRINT(AST_DestructuredKeyVal, print_key_value);
DEFPRINT(AST_DestructuredObject, function(output) {
var props = this.properties;
if (props.length > 0) output.with_block(function() {
props.forEach(function(prop, i) {
if (i) {
output.print(",");
output.newline();
}
output.indent();
prop.print(output);
});
output.newline();
});
else print_braced_empty(this, output);
});
DEFPRINT(AST_Object, function(output) {
var props = this.properties;
if (props.length > 0) output.with_block(function() {
@@ -1314,12 +1356,13 @@ function OutputStream(options) {
}
}
DEFPRINT(AST_ObjectKeyVal, function(output) {
function print_key_value(output) {
var self = this;
print_property_key(self, output);
output.colon();
self.value.print(output);
});
}
DEFPRINT(AST_ObjectKeyVal, print_key_value);
function print_accessor(type) {
return function(output) {
var self = this;
@@ -1483,6 +1526,7 @@ function OutputStream(options) {
AST_Constant,
AST_Debugger,
AST_Definitions,
AST_Destructured,
AST_Finally,
AST_Jump,
AST_Lambda,
@@ -1497,7 +1541,7 @@ function OutputStream(options) {
output.add_mapping(this.start);
});
DEFMAP([ AST_ObjectProperty ], function(output) {
DEFMAP([ AST_DestructuredKeyVal, AST_ObjectProperty ], function(output) {
if (typeof this.key == "string") output.add_mapping(this.start, this.key);
});
})();

View File

@@ -973,14 +973,16 @@ function parse($TEXT, options) {
if (!is("punc", ";")) {
init = is("keyword", "const")
? (next(), const_(true))
: is("keyword", "let")
? (next(), let_(true))
: is("keyword", "var")
? (next(), var_(true))
: expression(true, true);
if (is("operator", "in")) {
if (init instanceof AST_Var) {
if (init instanceof AST_Definitions) {
if (init.definitions.length > 1)
croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
} else if (!is_assignable(init)) {
} else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) {
croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
}
next();
@@ -1025,7 +1027,7 @@ function parse($TEXT, options) {
var argnames = [];
for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(",");
argnames.push(as_symbol(AST_SymbolFunarg));
argnames.push(maybe_destructured(AST_SymbolFunarg));
}
next();
var loop = S.in_loop;
@@ -1146,16 +1148,16 @@ function parse($TEXT, options) {
});
}
function vardefs(type, no_in, must_init) {
function vardefs(type, no_in) {
var a = [];
for (;;) {
var start = S.token;
var name = as_symbol(type);
var name = maybe_destructured(type);
var value = null;
if (is("operator", "=")) {
next();
value = expression(false, no_in);
} else if (must_init) {
} else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) {
croak("Missing initializer in declaration");
}
a.push(new AST_VarDef({
@@ -1174,7 +1176,7 @@ function parse($TEXT, options) {
var const_ = function(no_in) {
return new AST_Const({
start : prev(),
definitions : vardefs(AST_SymbolConst, no_in, true),
definitions : vardefs(AST_SymbolConst, no_in),
end : prev()
});
};
@@ -1306,7 +1308,8 @@ function parse($TEXT, options) {
unexpected();
};
function expr_list(closing, allow_trailing_comma, allow_empty) {
function expr_list(closing, allow_trailing_comma, allow_empty, parser) {
if (!parser) parser = expression;
var first = true, a = [];
while (!is("punc", closing)) {
if (first) first = false; else expect(",");
@@ -1314,7 +1317,7 @@ function parse($TEXT, options) {
if (is("punc", ",") && allow_empty) {
a.push(new AST_Hole({ start: S.token, end: S.token }));
} else {
a.push(expression(false));
a.push(parser());
}
}
next();
@@ -1449,6 +1452,54 @@ function parse($TEXT, options) {
return sym;
}
function maybe_destructured(type) {
var start = S.token;
if (is("punc", "[")) {
next();
return new AST_DestructuredArray({
start: start,
elements: expr_list("]", !options.strict, true, function() {
return maybe_destructured(type);
}),
end: prev(),
});
}
if (is("punc", "{")) {
next();
var first = true, a = [];
while (!is("punc", "}")) {
if (first) first = false; else expect(",");
// allow trailing comma
if (!options.strict && is("punc", "}")) break;
var key_start = S.token;
var key = as_property_key();
if (!is("punc", ":") && key_start.type == "name") {
a.push(new AST_DestructuredKeyVal({
start: key_start,
key: key,
value: _make_symbol(type, key_start),
end: prev(),
}));
continue;
}
expect(":");
a.push(new AST_DestructuredKeyVal({
start: key_start,
key: key,
value: maybe_destructured(type),
end: prev(),
}));
}
next();
return new AST_DestructuredObject({
start: start,
properties: a,
end: prev(),
});
}
return as_symbol(type);
}
function mark_pure(call) {
var start = call.start;
var comments = start.comments_before;
@@ -1578,11 +1629,43 @@ function parse($TEXT, options) {
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef;
}
function to_destructured(node) {
if (node instanceof AST_Array) {
var elements = node.elements.map(to_destructured);
return all(elements, function(node) {
return node instanceof AST_Destructured || node instanceof AST_Hole || is_assignable(node);
}) ? new AST_DestructuredArray({
start: node.start,
elements: elements,
end: node.end,
}) : node;
}
if (!(node instanceof AST_Object)) return node;
var props = [];
for (var i = 0; i < node.properties.length; i++) {
var prop = node.properties[i];
if (!(prop instanceof AST_ObjectKeyVal)) return node;
var value = to_destructured(prop.value);
if (!(value instanceof AST_Destructured || is_assignable(value))) return node;
props.push(new AST_DestructuredKeyVal({
start: prop.start,
key: prop.key,
value: value,
end: prop.end,
}));
}
return new AST_DestructuredObject({
start: node.start,
properties: props,
end: node.end,
});
}
var maybe_assign = function(no_in) {
var start = S.token;
var left = maybe_conditional(no_in), val = S.token.value;
if (is("operator") && ASSIGNMENT[val]) {
if (is_assignable(left)) {
if (is_assignable(left) || val == "=" && (left = to_destructured(left)) instanceof AST_Destructured) {
next();
return new AST_Assign({
start : start,

View File

@@ -72,7 +72,7 @@ SymbolDef.prototype = {
if (def) {
this.mangled_name = def.mangled_name || def.name;
} else {
this.mangled_name = next_mangled_name(this.scope, options, this);
this.mangled_name = next_mangled_name(this, options);
}
if (this.global && cache) {
cache.set(this.name, this.mangled_name);
@@ -204,7 +204,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
// pass 2: find back references and eval
self.globals = new Dictionary();
var in_arg = [];
var tw = new TreeWalker(function(node) {
if (node instanceof AST_Lambda) {
in_arg.push(node);
node.argnames.forEach(function(argname) {
argname.walk(tw);
});
in_arg.pop();
walk_body(node, tw);
return true;
}
if (node instanceof AST_LoopControl) {
if (node.label) node.label.thedef.references.push(node);
return true;
@@ -212,10 +222,24 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (node instanceof AST_SymbolRef) {
var name = node.name;
var sym = node.scope.find_variable(name);
for (var i = in_arg.length; i > 0 && sym;) {
i = in_arg.lastIndexOf(sym.scope, i - 1);
if (i < 0) break;
var decl = sym.orig[0];
if (decl instanceof AST_SymbolFunarg || decl instanceof AST_SymbolLambda) {
node.in_arg = true;
break;
}
sym = sym.scope.parent_scope.find_variable(name);
}
if (!sym) {
sym = self.def_global(node);
} else if (name == "arguments" && sym.scope instanceof AST_Lambda) {
sym.scope.uses_arguments = true;
if (!(tw.parent() instanceof AST_PropAccess)) {
sym.scope.uses_arguments = "d";
} else if (!sym.scope.uses_arguments) {
sym.scope.uses_arguments = true;
}
}
if (name == "eval") {
var parent = tw.parent();
@@ -408,7 +432,8 @@ function names_in_use(scope, options) {
return names;
}
function next_mangled_name(scope, options, def) {
function next_mangled_name(def, options) {
var scope = def.scope;
var in_use = names_in_use(scope, options);
var holes = scope.cname_holes;
var names = Object.create(null);
@@ -461,6 +486,7 @@ function _default_mangler_options(options) {
keep_fnames : false,
reserved : [],
toplevel : false,
webkit : false,
});
if (!Array.isArray(options.reserved)) options.reserved = [];
// Never mangle arguments
@@ -495,9 +521,24 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
return true;
}
if (node instanceof AST_BlockScope) {
var to_mangle = [];
if (options.webkit && node instanceof AST_IterationStatement && node.init instanceof AST_Let) {
node.init.definitions.forEach(function(defn) {
defn.name.match_symbol(function(sym) {
if (!(sym instanceof AST_SymbolLet)) return;
var def = sym.definition();
var scope = sym.scope.parent_scope;
var redef = scope.def_variable(sym);
sym.thedef = def;
scope.to_mangle.push(redef);
def.redefined = function() {
return redef;
};
});
}, true);
}
node.to_mangle = [];
node.variables.each(function(def) {
if (!defer_redef(def)) to_mangle.push(def);
if (!defer_redef(def)) node.to_mangle.push(def);
});
descend();
if (options.cache && node instanceof AST_Toplevel) {
@@ -508,7 +549,7 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
sym.scope = node;
sym.reference(options);
}
to_mangle.forEach(mangle);
node.to_mangle.forEach(mangle);
return true;
}
if (node instanceof AST_Label) {
@@ -528,13 +569,19 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
def.mangle(options);
}
function defer_redef(def, node) {
function defer_redef(def) {
var sym = def.orig[0];
var redef = def.redefined();
if (!redef) return false;
if (!redef) {
if (!(sym instanceof AST_SymbolConst)) return false;
var scope = def.scope.resolve();
if (def.scope === scope) return false;
redef = scope.def_variable(sym);
scope.to_mangle.push(redef);
}
redefined.push(def);
def.references.forEach(reference);
var node = def.orig[0];
if (node instanceof AST_SymbolCatch || node instanceof AST_SymbolConst) reference(node);
if (sym instanceof AST_SymbolCatch || sym instanceof AST_SymbolConst) reference(sym);
return true;
function reference(sym) {

View File

@@ -160,6 +160,16 @@ TreeTransformer.prototype = new TreeWalker;
DEF(AST_Array, function(self, tw) {
self.elements = do_list(self.elements, tw);
});
DEF(AST_DestructuredArray, function(self, tw) {
self.elements = do_list(self.elements, tw);
});
DEF(AST_DestructuredKeyVal, function(self, tw) {
if (self.key instanceof AST_Node) self.key = self.key.transform(tw);
self.value = self.value.transform(tw);
});
DEF(AST_DestructuredObject, function(self, tw) {
self.properties = do_list(self.properties, tw);
});
DEF(AST_Object, function(self, tw) {
self.properties = do_list(self.properties, 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.11.6",
"version": "3.12.0",
"engines": {
"node": ">=0.8.0"
},

View File

@@ -63,7 +63,6 @@ function make_code(ast, options) {
function parse_test(file) {
var script = fs.readFileSync(file, "utf8");
// TODO try/catch can be removed after fixing https://github.com/mishoo/UglifyJS/issues/348
try {
var ast = U.parse(script, {
filename: file

View File

@@ -807,3 +807,47 @@ issue_4200: {
}
expect_stdout: "undefined"
}
issue_4291_1: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
console.log(function() {
arguments[0] = "PASS";
return arguments;
}()[0]);
}
expect: {
console.log(function() {
arguments[0] = "PASS";
return arguments;
}()[0]);
}
expect_stdout: "PASS"
}
issue_4291_2: {
options = {
arguments: true,
keep_fargs: "strict",
}
input: {
var a = function() {
if (arguments[0])
arguments[1] = "PASS";
return arguments;
}(42);
console.log(a[1], a[0], a.length);
}
expect: {
var a = function(argument_0) {
if (argument_0)
arguments[1] = "PASS";
return arguments;
}(42);
console.log(a[1], a[0], a.length);
}
expect_stdout: "PASS 42 1"
}

View File

@@ -1,3 +1,45 @@
mangle_block: {
mangle = {
toplevel: false,
}
input: {
var o = "PASS";
{
const a = "FAIL";
}
console.log(o);
}
expect: {
var o = "PASS";
{
const a = "FAIL";
}
console.log(o);
}
expect_stdout: "PASS"
}
mangle_block_toplevel: {
mangle = {
toplevel: true,
}
input: {
var o = "PASS";
{
const a = "FAIL";
}
console.log(o);
}
expect: {
var o = "PASS";
{
const c = "FAIL";
}
console.log(o);
}
expect_stdout: "PASS"
}
mangle_catch_1: {
mangle = {}
input: {
@@ -11,8 +53,8 @@ mangle_catch_1: {
expect: {
try {
throw "eeeee";
} catch (e) {
const o = typeof d;
} catch (o) {
const e = typeof d;
}
console.log(typeof a, typeof b);
}
@@ -57,6 +99,23 @@ retain_block: {
expect_stdout: true
}
retain_catch: {
options = {
dead_code: true,
}
input: {
try {} catch (a) {
const a = "aa";
}
}
expect: {
try {} catch (a) {
const a = "aa";
}
}
expect_stdout: true
}
if_dead_branch: {
options = {
conditionals: true,
@@ -1225,3 +1284,66 @@ issue_4274_2: {
}
expect_stdout: true
}
issue_4290_1: {
options = {
unused: true,
}
input: {
const a = 0;
var a;
}
expect: {
const a = 0;
var a;
}
expect_stdout: true
}
issue_4305_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
const arguments = function() {
while (console.log("PASS"));
};
arguments();
})();
}
expect: {
(function() {
const arguments = function() {
while (console.log("PASS"));
};
arguments();
})();
}
expect_stdout: true
}
issue_4305_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function(a) {
const a = function() {
while (console.log("aaaaa"));
};
a();
})();
}
expect: {
(function(a) {
const a = function() {
while (console.log("aaaaa"));
};
a();
})();
}
expect_stdout: true
}

File diff suppressed because it is too large Load Diff

View File

@@ -521,7 +521,7 @@ issue_2531_2: {
options = {
evaluate: true,
inline: true,
passes: 3,
passes: 2,
reduce_funcs: true,
reduce_vars: true,
side_effects: true,
@@ -556,9 +556,10 @@ issue_2531_3: {
options = {
evaluate: true,
inline: true,
passes: 3,
passes: 2,
reduce_funcs: true,
reduce_vars: true,
sequences: true,
side_effects: true,
toplevel: true,
unused: true,

View File

@@ -297,6 +297,33 @@ name_collision_3: {
expect_stdout: "true 4 6"
}
name_collision_4: {
options = {
hoist_props: true,
reduce_vars: true,
}
input: {
console.log(function() {
var o = {
p: 0,
q: "PASS",
};
return function(o_p) {
if (!o.p) return o_p;
}(o.q);
}());
}
expect: {
console.log(function() {
var o_p$0 = 0, o_q = "PASS";
return function(o_p) {
if (!o_p$0) return o_p;
}(o_q);
}());
}
expect_stdout: "PASS"
}
contains_this_1: {
options = {
evaluate: true,

View File

@@ -20,6 +20,26 @@ retain_block: {
node_version: ">=4"
}
retain_catch: {
options = {
dead_code: true,
}
input: {
"use strict";
try {} catch (a) {
let a = "aa";
}
}
expect: {
"use strict";
try {} catch (a) {
let a = "aa";
}
}
expect_stdout: true
node_version: ">=4"
}
if_dead_branch: {
options = {
conditionals: true,
@@ -1006,3 +1026,205 @@ issue_4274_2: {
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4276_1: {
options = {
unused: true,
}
input: {
"use strict";
try {
let a = b, b;
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
let a = b, b;
console.log("FAIL");
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4276_2: {
options = {
unused: true,
}
input: {
"use strict";
try {
let a = f(), b;
console.log("FAIL");
function f() {
return b;
}
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
let a = f(), b;
console.log("FAIL");
function f() {
return b;
}
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4290_1: {
options = {
unused: true,
}
input: {
"use strict";
let a;
var a;
}
expect: {
"use strict";
let a;
var a;
}
expect_stdout: true
node_version: ">=4"
}
issue_4290_2: {
options = {
collapse_vars: true,
}
input: {
"use strict";
try {
console.log(function(a) {
a = c;
let c;
return a;
}());
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
console.log(function(a) {
a = c;
let c;
return a;
}());
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_4305_1: {
options = {
reduce_vars: true,
unused: true,
}
input: {
(function() {
let arguments = function() {
while (console.log("PASS"));
};
arguments();
})();
}
expect: {
(function() {
let arguments = function() {
while (console.log("PASS"));
};
arguments();
})();
}
expect_stdout: true
node_version: ">=6"
}
issue_4305_2: {
options = {
reduce_vars: true,
unused: true,
}
input: {
"use strict";
(function(a) {
let a = function() {
while (console.log("aaaaa"));
};
a();
})();
}
expect: {
"use strict";
(function(a) {
let a = function() {
while (console.log("aaaaa"));
};
a();
})();
}
expect_stdout: true
node_version: ">=4"
}
issue_1753: {
mangle = {
toplevel: false,
webkit: true,
}
input: {
"use strict";
let l = null;
for (let i = 0; i < 1; i++)
console.log(i);
}
expect: {
"use strict";
let l = null;
for (let i = 0; i < 1; i++)
console.log(i);
}
expect_stdout: "0"
node_version: ">=4"
}
issue_1753_toplevel: {
mangle = {
toplevel: true,
webkit: true,
}
input: {
"use strict";
let l = null;
for (let i = 0; i < 1; i++)
console.log(i);
}
expect: {
"use strict";
let l = null;
for (let e = 0; e < 1; e++)
console.log(e);
}
expect_stdout: "0"
node_version: ">=4"
}

View File

@@ -1,49 +1,100 @@
hex_numbers_in_parentheses_for_prototype_functions: {
parentheses_for_prototype_functions: {
beautify = {
beautify: true,
}
input: {
function f() {
(-2);
(-2).toFixed(0);
(function() {
console.log((-2));
console.log((-2).toFixed(0));
(2);
(2).toFixed(0);
console.log((2));
console.log((2).toFixed(0));
(0.2);
(0.2).toFixed(0);
console.log((0.2));
console.log((0.2).toFixed(0));
(2.34e20);
(2.34e20).toFixed(0);
console.log((2.34e20));
console.log((2.34e20).toFixed(0));
(0.00000002);
(0.00000002).toFixed(0);
console.log((0.00000002));
console.log((0.00000002).toFixed(0));
(1000000000000000128);
(1000000000000000128).toFixed(0);
console.log((1000000000000000128));
console.log((1000000000000000128).toFixed(0));
(-1000000000000000128);
(-1000000000000000128).toFixed(0);
}
console.log((-1000000000000000128));
console.log((-1000000000000000128).toFixed(0));
})();
}
expect_exact: [
"function f() {",
" -2;",
" (-2).toFixed(0);",
" 2;",
" 2..toFixed(0);",
" .2;",
" .2.toFixed(0);",
" 234e18;",
" 234e18.toFixed(0);",
" 2e-8;",
" 2e-8.toFixed(0);",
" 0xde0b6b3a7640080;",
" (0xde0b6b3a7640080).toFixed(0);",
" -0xde0b6b3a7640080;",
" (-0xde0b6b3a7640080).toFixed(0);",
"}",
"(function() {",
" console.log(-2);",
" console.log((-2).toFixed(0));",
" console.log(2);",
" console.log(2..toFixed(0));",
" console.log(.2);",
" console.log(.2.toFixed(0));",
" console.log(234e18);",
" console.log(234e18.toFixed(0));",
" console.log(2e-8);",
" console.log(2e-8.toFixed(0));",
" console.log(0xde0b6b3a7640080);",
" console.log(0xde0b6b3a7640080.toFixed(0));",
" console.log(-0xde0b6b3a7640080);",
" console.log((-0xde0b6b3a7640080).toFixed(0));",
"})();",
]
expect_stdout: true
}
parentheses_for_prototype_functions_galio: {
beautify = {
beautify: true,
galio: true,
}
input: {
(function() {
console.log((-2));
console.log((-2).toFixed(0));
console.log((2));
console.log((2).toFixed(0));
console.log((0.2));
console.log((0.2).toFixed(0));
console.log((2.34e20));
console.log((2.34e20).toFixed(0));
console.log((0.00000002));
console.log((0.00000002).toFixed(0));
console.log((1000000000000000128));
console.log((1000000000000000128).toFixed(0));
console.log((-1000000000000000128));
console.log((-1000000000000000128).toFixed(0));
})();
}
expect_exact: [
"(function() {",
" console.log(-2);",
" console.log((-2).toFixed(0));",
" console.log(2);",
" console.log(2..toFixed(0));",
" console.log(.2);",
" console.log(.2.toFixed(0));",
" console.log(234e18);",
" console.log(234e18.toFixed(0));",
" console.log(2e-8);",
" console.log(2e-8.toFixed(0));",
" console.log(0xde0b6b3a7640080);",
" console.log((0xde0b6b3a7640080).toFixed(0));",
" console.log(-0xde0b6b3a7640080);",
" console.log((-0xde0b6b3a7640080).toFixed(0));",
"})();",
]
expect_stdout: true
}
comparisons: {

View File

@@ -234,3 +234,178 @@ issue_4191_let: {
expect_stdout: "function undefined"
node_version: ">=4"
}
forin_const_1: {
options = {
join_vars: true,
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
const o = {
foo: 42,
bar: "PASS",
};
for (const k in o)
console.log(k, o[k]);
}
expect: {
var o = {
foo: 42,
bar: "PASS",
};
for (const k in o)
console.log(k, o[k]);
}
expect_stdout: true
node_version: ">=4"
}
forin_const_2: {
options = {
join_vars: true,
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
const o = {
p: 42,
q: "PASS",
};
for (const [ k ] in o)
console.log(k, o[k]);
}
expect: {
var o = {
p: 42,
q: "PASS",
}, k;
for ([ k ] in o)
console.log(k, o[k]);
}
expect_stdout: [
"p 42",
"q PASS",
]
node_version: ">=6"
}
forin_let_1: {
options = {
join_vars: true,
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
"use strict";
let o = {
foo: 42,
bar: "PASS",
};
for (let k in o)
console.log(k, o[k]);
}
expect: {
"use strict";
var o = {
foo: 42,
bar: "PASS",
}, k;
for (k in o)
console.log(k, o[k]);
}
expect_stdout: [
"foo 42",
"bar PASS",
]
node_version: ">=4"
}
forin_let_2: {
options = {
join_vars: true,
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
let o = {
p: 42,
q: "PASS",
};
for (let [ k ] in o)
console.log(k, o[k]);
}
expect: {
var o = {
p: 42,
q: "PASS",
}, k;
for ([ k ] in o)
console.log(k, o[k]);
}
expect_stdout: [
"p 42",
"q PASS",
]
node_version: ">=6"
}
issue_4290_1_const: {
options = {
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
const a = 0;
var a;
}
expect: {
const a = 0;
var a;
}
expect_stdout: true
}
issue_4290_1_let: {
options = {
reduce_vars: true,
toplevel: true,
varify: true,
}
input: {
"use strict";
let a = 0;
var a;
}
expect: {
"use strict";
let a = 0;
var a;
}
expect_stdout: true
node_version: ">=4"
}
drop_forin_let: {
options = {
loops: true,
toplevel: true,
unused: true,
varify: true,
}
input: {
"use strict";
for (let a in console.log("PASS"));
}
expect: {
"use strict";
console.log("PASS");
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@@ -95,6 +95,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// quick ignores
if (node instanceof U.AST_Accessor) return;
if (node instanceof U.AST_Destructured) return;
if (node instanceof U.AST_Directive) return;
if (!in_list && node instanceof U.AST_EmptyStatement) return;
if (node instanceof U.AST_Label) return;
@@ -114,6 +115,8 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
// ignore lvalues
if (parent instanceof U.AST_Assign && parent.left === node) return;
if (parent instanceof U.AST_DestructuredArray) return;
if (parent instanceof U.AST_DestructuredKeyVal && parent.value === node) return;
if (parent instanceof U.AST_Unary && parent.expression === node) switch (parent.operator) {
case "++":
case "--":
@@ -250,13 +253,23 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
}
}
else if (node instanceof U.AST_ForIn) {
var expr = [
node.init,
node.object,
node.body,
][ (node.start._permute * steps | 0) % 3 ];
var expr;
switch ((node.start._permute * steps | 0) % 3) {
case 0:
if (!(node.init instanceof U.AST_Definitions
&& node.init.definitions[0].name instanceof U.AST_Destructured)) {
expr = node.init;
}
break;
case 1:
expr = node.object;
break;
case 2:
if (!has_loopcontrol(node.body, node, parent)) expr = node.body;
break;
}
node.start._permute += step;
if (expr && (expr !== node.body || !has_loopcontrol(expr, node, parent))) {
if (expr) {
CHANGED = true;
return to_statement(expr);
}
@@ -389,6 +402,13 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
CHANGED = true;
return List.skip;
}
// skip element/property from (destructured) array/object
if (parent instanceof U.AST_Array || parent instanceof U.AST_Destructured || parent instanceof AST_Object) {
node.start._permute++;
CHANGED = true;
return List.skip;
}
}
// replace this node

View File

@@ -377,6 +377,118 @@ function createArgs(recurmax, stmtDepth, canThrow) {
return args.join(", ");
}
function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, maybe, dontStore) {
var avoid = [];
var len = unique_vars.length;
var pairs = createPairs(recurmax);
unique_vars.length = len;
return pairs;
function createAssignmentValue(recurmax) {
var current = VAR_NAMES;
VAR_NAMES = (varNames || VAR_NAMES).slice();
var value = varNames && rng(2) ? createValue() : createExpression(recurmax, noComma, stmtDepth, canThrow);
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;
var key;
do {
key = createObjectKey(recurmax, stmtDepth, canThrow);
} while (keys.indexOf(key) >= 0);
VAR_NAMES = save.concat(VAR_NAMES.slice(len));
return key;
}
function createPairs(recurmax) {
var names = [], values = [];
var m = rng(4), n = rng(4);
if (!varNames) m = Math.max(m, n, 1);
for (var i = Math.max(m, n); --i >= 0;) {
if (i < m && i < n) {
createDestructured(recurmax, names, values);
continue;
}
if (i < m) {
unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
var name = createVarName(maybe, dontStore);
unique_vars.length -= 6;
avoid.push(name);
unique_vars.push(name);
names.unshift(name);
}
if (i < n) {
values.unshift(createAssignmentValue(recurmax));
}
}
return {
names: names,
values: values,
};
}
function createDestructured(recurmax, names, values) {
switch (rng(20)) {
case 0:
if (--recurmax < 0) {
names.unshift("[]");
values.unshift('""');
} else {
var pairs = createPairs(recurmax);
while (!rng(10)) {
var index = rng(pairs.names.length + 1);
pairs.names.splice(index, 0, "");
pairs.values.splice(index, 0, rng(2) ? createAssignmentValue(recurmax) : "");
}
names.unshift("[ " + pairs.names.join(", ") + " ]");
values.unshift("[ " + pairs.values.join(", ") + " ]");
}
break;
case 1:
if (--recurmax < 0) {
names.unshift("{}");
values.unshift('""');
} else {
var pairs = createPairs(recurmax);
var keys = [];
pairs.names.forEach(function(name, index) {
if (/^[[{]/.test(name)) {
var key;
do {
key = KEYS[rng(KEYS.length)];
} while (keys.indexOf(key) >= 0);
keys[index] = key;
}
});
names.unshift("{ " + 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) {
var key = index in keys ? keys[index] : createKey(recurmax, keys);
return key + ": " + value;
}).join(", ") + " }");
}
break;
default:
unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity");
var name = createVarName(maybe, dontStore);
unique_vars.length -= 6;
avoid.push(name);
unique_vars.push(name);
names.unshift(name);
values.unshift(createAssignmentValue(recurmax));
break;
}
}
}
function filterDirective(s) {
if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ";" + s[2];
return s;
@@ -415,11 +527,37 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
return names.indexOf(name) < 0;
});
var len = VAR_NAMES.length;
var s = type + " " + names.map(function(name) {
var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
VAR_NAMES.push(name);
return name + " = " + value;
}).join(", ") + ";";
var s = type + " ";
switch (rng(10)) {
case 0:
while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
s += "[ " + names.join(", ") + " ] = [ " + names.map(function() {
return rng(10) ? createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) : "";
}).join(", ") + " ];";
break;
case 1:
var keys = [];
s += "{ " + names.map(function(name, i) {
var key = createObjectKey(recurmax, stmtDepth, canThrow);
if (!/\[/.test(key)) keys[i] = key;
return key + ": " + name;
}).join(", ") + "} = { " + names.map(function(name, i) {
var key = i in keys ? keys[i] : createObjectKey(recurmax, stmtDepth, canThrow);
return key + ": " + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
}).join(", ") + "};";
break;
default:
s += names.map(function(name, i) {
if (type == "let" && !rng(10)) {
VAR_NAMES.push(name);
return name;
}
var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow);
VAR_NAMES.push(name);
return name + " = " + value;
}).join(", ") + ";";
break;
}
VAR_NAMES = save.concat(VAR_NAMES.slice(len));
return s;
}
@@ -429,9 +567,9 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
if (--recurmax < 0) { return ";"; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var s = [];
var name;
var name, args;
var varNames = VAR_NAMES.slice();
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
var namesLenBefore = VAR_NAMES.length;
if (allowDefun || rng(5) > 0) {
name = "f" + funcs++;
} else {
@@ -439,7 +577,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
name = createVarName(MANDATORY, !allowDefun);
unique_vars.length -= 3;
}
s.push("function " + name + "(" + createParams() + "){", strictMode());
var params;
if ((!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(", ");
} else {
params = createParams();
}
s.push("function " + name + "(" + params + "){", strictMode());
s.push(defns());
if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess.
@@ -450,17 +597,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
}
s.push("}", "");
s = filterDirective(s).join("\n");
VAR_NAMES.length = namesLenBefore;
});
VAR_NAMES = varNames;
if (!allowDefun) {
// avoid "function statements" (decl inside statements)
s = "var " + createVarName(MANDATORY) + " = " + s;
s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
} else if (!(name in called) || rng(3) > 0) {
s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
} else if (!(name in called) || args || rng(3)) {
s += "var " + createVarName(MANDATORY) + " = " + name;
s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
}
return s + ";";
@@ -561,8 +707,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return [
"{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
label.target + " for (",
/^key/.test(key) ? "var " : "",
key + " in expr" + loop + ") {",
!/^key/.test(key) ? rng(10) ? "" : "var " : rng(10) ? "var " : rng(2) ? "let " : "const ",
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),
"}}",
@@ -576,7 +723,12 @@ 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:
switch (rng(3)) {
if (!rng(20)) {
var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow, null, MANDATORY);
return "var " + pairs.names.map(function(name, index) {
return index in pairs.values ? name + " = " + pairs.values[index] : name;
}).join(", ") + ";";
} else switch (rng(3)) {
case 0:
unique_vars.push("c");
var name = createVarName(MANDATORY);
@@ -719,7 +871,28 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
return getVarName();
case p++:
return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
switch (rng(20)) {
case 0:
return [
"[ ",
new Array(rng(3)).join(","),
getVarName(NO_CONST),
new Array(rng(3)).join(","),
" ] = ",
createArrayLiteral(recurmax, stmtDepth, canThrow),
].join("");
case 1:
return [
"{ ",
rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ",
getVarName(NO_CONST),
" } = ",
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow),
" || {}",
].join("");
default:
return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
}
case p++:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++:
@@ -864,7 +1037,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++:
case p++:
case p++:
var name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
var name;
do {
name = rng(3) == 0 ? getVarName() : "f" + rng(funcs + 2);
} while (name in called && !called[name]);
called[name] = true;
return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")";
}
@@ -1002,13 +1178,77 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case 3:
assignee = getVarName();
expr = "(" + assignee + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)
+ "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
switch (rng(20)) {
case 0:
expr = [
"([ ",
assignee,
"[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
" ] = [ ",
_createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
" ])",
].join("");
break;
case 1:
var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
expr = [
"({ ",
key1, ": ", assignee,
"[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
" } = { ",
key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
" })",
].join("");
break;
default:
expr = [
"(",
assignee,
"[", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), "]",
createAssignment(),
_createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
")",
].join("");
break;
}
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
case 4:
assignee = getVarName();
expr = "(" + assignee + "." + getDotKey(true) + createAssignment()
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
switch (rng(20)) {
case 0:
expr = [
"([ ",
assignee,
".", getDotKey(true),
" ] = [ ",
_createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
" ])",
].join("");
break;
case 1:
var key1 = createObjectKey(recurmax, stmtDepth, canThrow);
var key2 = /^\[/.test(key1) ? createObjectKey(recurmax, stmtDepth, canThrow) : key1;
expr = [
"({ ",
key1, ": ", assignee,
".", getDotKey(true),
" } = { ",
key2, ": ", _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
" })",
].join("");
break;
default:
expr = [
"(",
assignee,
".", getDotKey(true),
createAssignment(),
_createBinaryExpr(recurmax, noComma, stmtDepth, canThrow),
")",
].join("");
break;
}
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
@@ -1351,7 +1591,14 @@ function patch_try_catch(orig, toplevel) {
}
}
var minify_options = require("./options.json").map(JSON.stringify);
var minify_options = require("./options.json");
if (typeof sandbox.run_code("console.log([ 1 ], {} = 2);") != "string") {
minify_options.forEach(function(o) {
if (!("output" in o)) o.output = {};
o.output.v8 = true;
});
}
minify_options = minify_options.map(JSON.stringify);
var original_code, original_result, errored;
var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) {