support destructured literals (#4278)

This commit is contained in:
Alex Lam S.L
2020-11-17 00:01:24 +00:00
committed by GitHub
parent 42e34c870a
commit e5f80afc53
10 changed files with 2477 additions and 305 deletions

View File

@@ -906,6 +906,8 @@ can pass additional arguments that control the code output:
- `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts) - `shebang` (default `true`) -- preserve shebang `#!` in preamble (bash scripts)
- `v8` (default `false`) -- enable workarounds for Chrome & Node.js bugs
- `webkit` (default `false`) -- enable workarounds for WebKit bugs. - `webkit` (default `false`) -- enable workarounds for WebKit bugs.
PhantomJS users should set this option to `true`. PhantomJS users should set this option to `true`.
@@ -1138,7 +1140,7 @@ To enable fast minify mode with the API use:
UglifyJS.minify(code, { compress: false, mangle: true }); 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 Various `compress` transforms that simplify, rearrange, inline and remove code
are known to have an adverse effect on debugging with source maps. This is are known to have an adverse effect on debugging with source maps. This is
@@ -1150,6 +1152,10 @@ disable the Uglify `compress` option and just use `mangle`.
To allow for better optimizations, the compiler makes various assumptions: 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 - `.toString()` and `.valueOf()` don't have side effects, and for built-in
objects they have not been overridden. objects they have not been overridden.
- `undefined`, `NaN` and `Infinity` have not been externally redefined. - `undefined`, `NaN` and `Infinity` have not been externally redefined.
@@ -1177,3 +1183,7 @@ To allow for better optimizations, the compiler makes various assumptions:
top.B = "PASS"; top.B = "PASS";
console.log(B); 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

@@ -414,8 +414,12 @@ var AST_ForIn = DEFNODE("ForIn", "init object", {
_validate: function() { _validate: function() {
if (this.init instanceof AST_Definitions) { if (this.init instanceof AST_Definitions) {
if (this.init.definitions.length != 1) throw new Error("init must have single declaration"); 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)) { } else {
throw new Error("init must be assignable"); 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"); must_be_expression(this, "object");
}, },
@@ -496,12 +500,26 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
} }
}, AST_Scope); }, 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", $documentation: "Base class for functions",
$propdoc: { $propdoc: {
name: "[AST_SymbolDeclaration?] the name of this function", name: "[AST_SymbolDeclaration?] the name of this function",
argnames: "[AST_SymbolFunarg*] array of function arguments", 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" 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_DestructuredObject) {
node.properties.forEach(function(prop) {
prop.value.walk(tw);
});
return true;
}
if (node instanceof AST_SymbolFunarg) visit(node);
});
this.argnames.forEach(function(argname) {
argname.walk(tw);
});
}, },
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
@@ -515,7 +533,9 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments length_read", {
}, },
_validate: function() { _validate: function() {
this.argnames.forEach(function(node) { 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); }, AST_Scope);
@@ -748,8 +768,10 @@ var AST_Const = DEFNODE("Const", null, {
_validate: function() { _validate: function() {
this.definitions.forEach(function(node) { this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); 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"); validate_destructured(node.name, function(node) {
must_be_expression(node, "value"); if (!(node instanceof AST_SymbolConst)) throw new Error("name must be AST_SymbolConst");
});
if (node.value != null) must_be_expression(node, "value");
}); });
}, },
}, AST_Definitions); }, AST_Definitions);
@@ -759,7 +781,9 @@ var AST_Let = DEFNODE("Let", null, {
_validate: function() { _validate: function() {
this.definitions.forEach(function(node) { this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); 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"); if (node.value != null) must_be_expression(node, "value");
}); });
}, },
@@ -770,7 +794,9 @@ var AST_Var = DEFNODE("Var", null, {
_validate: function() { _validate: function() {
this.definitions.forEach(function(node) { this.definitions.forEach(function(node) {
if (!(node instanceof AST_VarDef)) throw new Error("definitions must be AST_VarDef[]"); 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"); if (node.value != null) must_be_expression(node, "value");
}); });
}, },
@@ -969,6 +995,14 @@ var AST_Assign = DEFNODE("Assign", null, {
$documentation: "An assignment expression — `a = b + 5`", $documentation: "An assignment expression — `a = b + 5`",
_validate: function() { _validate: function() {
if (this.operator.indexOf("=") < 0) throw new Error('operator must contain "="'); 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); }, AST_Binary);
@@ -992,6 +1026,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", { var AST_Object = DEFNODE("Object", "properties", {
$documentation: "An object literal", $documentation: "An object literal",
$propdoc: { $propdoc: {

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,7 @@ function OutputStream(options) {
semicolons : true, semicolons : true,
shebang : true, shebang : true,
source_map : null, source_map : null,
v8 : false,
webkit : false, webkit : false,
width : 80, width : 80,
wrap_iife : false, wrap_iife : false,
@@ -499,11 +500,11 @@ function OutputStream(options) {
} }
} }
if (/comment[134]/.test(c.type)) { if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' ') + "\n"); print("//" + c.value.replace(/[@#]__PURE__/g, " ") + "\n");
indent(); indent();
last_nlb = true; last_nlb = true;
} else if (c.type == "comment2") { } else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/"); print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
last_nlb = false; last_nlb = false;
} }
}); });
@@ -557,10 +558,10 @@ function OutputStream(options) {
space(); space();
} }
if (/comment[134]/.test(c.type)) { if (/comment[134]/.test(c.type)) {
print("//" + c.value.replace(/[@#]__PURE__/g, ' ')); print("//" + c.value.replace(/[@#]__PURE__/g, " "));
need_newline_indented = true; need_newline_indented = true;
} else if (c.type == "comment2") { } else if (c.type == "comment2") {
print("/*" + c.value.replace(/[@#]__PURE__/g, ' ') + "*/"); print("/*" + c.value.replace(/[@#]__PURE__/g, " ") + "*/");
need_space = true; need_space = true;
} }
}); });
@@ -610,7 +611,7 @@ function OutputStream(options) {
}, },
parent : function(n) { parent : function(n) {
return stack[stack.length - 2 - (n || 0)]; return stack[stack.length - 2 - (n || 0)];
} },
}; };
} }
@@ -652,13 +653,7 @@ function OutputStream(options) {
/* -----[ PARENTHESES ]----- */ /* -----[ PARENTHESES ]----- */
function PARENS(nodetype, func) { function PARENS(nodetype, func) {
if (Array.isArray(nodetype)) { nodetype.DEFMETHOD("needs_parens", func);
nodetype.forEach(function(nodetype) {
PARENS(nodetype, func);
});
} else {
nodetype.DEFMETHOD("needs_parens", func);
}
} }
PARENS(AST_Node, return_false); PARENS(AST_Node, return_false);
@@ -667,11 +662,11 @@ function OutputStream(options) {
// the first token to appear in a statement. // the first token to appear in a statement.
PARENS(AST_Function, function(output) { PARENS(AST_Function, function(output) {
if (!output.has_parens() && first_in_statement(output)) return true; if (!output.has_parens() && first_in_statement(output)) return true;
if (output.option('webkit')) { if (output.option("webkit")) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_PropAccess && p.expression === this) return true; if (p instanceof AST_PropAccess && p.expression === this) return true;
} }
if (output.option('wrap_iife')) { if (output.option("wrap_iife")) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_Call && p.expression === this) return true; if (p instanceof AST_Call && p.expression === this) return true;
} }
@@ -679,9 +674,10 @@ function OutputStream(options) {
// same goes for an object literal, because otherwise it would be // same goes for an object literal, because otherwise it would be
// interpreted as a block of code. // interpreted as a block of code.
PARENS(AST_Object, function(output) { function needs_parens_obj(output) {
return !output.has_parens() && first_in_statement(output); return !output.has_parens() && first_in_statement(output);
}); }
PARENS(AST_Object, needs_parens_obj);
PARENS(AST_Unary, function(output) { PARENS(AST_Unary, function(output) {
var p = output.parent(); var p = output.parent();
@@ -701,6 +697,7 @@ function OutputStream(options) {
|| p instanceof AST_Conditional || p instanceof AST_Conditional
// { [(1, 2)]: 3 }[2] ==> 3 // { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2 // { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_DestructuredKeyVal
|| p instanceof AST_ObjectProperty || p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2 // (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
|| p instanceof AST_PropAccess && p.expression === this || p instanceof AST_PropAccess && p.expression === this
@@ -747,7 +744,7 @@ function OutputStream(options) {
var p = output.parent(); var p = output.parent();
if (p instanceof AST_New) return p.expression === this; if (p instanceof AST_New) return p.expression === this;
// https://bugs.webkit.org/show_bug.cgi?id=123506 // https://bugs.webkit.org/show_bug.cgi?id=123506
if (output.option('webkit')) { if (output.option("webkit")) {
var g = output.parent(1); var g = output.parent(1);
return this.expression instanceof AST_Function return this.expression instanceof AST_Function
&& p instanceof AST_PropAccess && p instanceof AST_PropAccess
@@ -776,18 +773,29 @@ function OutputStream(options) {
} }
}); });
PARENS([ AST_Assign, AST_Conditional ], function(output) { function needs_parens_assign_cond(self, output) {
var p = output.parent(); var p = output.parent();
// 1 + (a = 2) + 3 → 6, side effect setting a = 2 // 1 + (a = 2) + 3 → 6, side effect setting a = 2
if (p instanceof AST_Binary) return !(p instanceof AST_Assign); if (p instanceof AST_Binary) return !(p instanceof AST_Assign);
// (a = func)() —or— new (a = Object)() // (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 // (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 // (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 // !(a = false) → true
if (p instanceof AST_Unary) return 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 ]----- */ /* -----[ PRINTERS ]----- */
@@ -1274,6 +1282,38 @@ function OutputStream(options) {
output.space(); output.space();
} : noop); } : 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) { DEFPRINT(AST_Object, function(output) {
var props = this.properties; var props = this.properties;
if (props.length > 0) output.with_block(function() { if (props.length > 0) output.with_block(function() {
@@ -1314,12 +1354,13 @@ function OutputStream(options) {
} }
} }
DEFPRINT(AST_ObjectKeyVal, function(output) { function print_key_value(output) {
var self = this; var self = this;
print_property_key(self, output); print_property_key(self, output);
output.colon(); output.colon();
self.value.print(output); self.value.print(output);
}); }
DEFPRINT(AST_ObjectKeyVal, print_key_value);
function print_accessor(type) { function print_accessor(type) {
return function(output) { return function(output) {
var self = this; var self = this;
@@ -1483,6 +1524,7 @@ function OutputStream(options) {
AST_Constant, AST_Constant,
AST_Debugger, AST_Debugger,
AST_Definitions, AST_Definitions,
AST_Destructured,
AST_Finally, AST_Finally,
AST_Jump, AST_Jump,
AST_Lambda, AST_Lambda,
@@ -1497,7 +1539,7 @@ function OutputStream(options) {
output.add_mapping(this.start); 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); 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", ";")) { if (!is("punc", ";")) {
init = is("keyword", "const") init = is("keyword", "const")
? (next(), const_(true)) ? (next(), const_(true))
: is("keyword", "let")
? (next(), let_(true))
: is("keyword", "var") : is("keyword", "var")
? (next(), var_(true)) ? (next(), var_(true))
: expression(true, true); : expression(true, true);
if (is("operator", "in")) { if (is("operator", "in")) {
if (init instanceof AST_Var) { if (init instanceof AST_Definitions) {
if (init.definitions.length > 1) if (init.definitions.length > 1)
croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos); 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); croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
} }
next(); next();
@@ -1025,7 +1027,7 @@ function parse($TEXT, options) {
var argnames = []; var argnames = [];
for (var first = true; !is("punc", ")");) { for (var first = true; !is("punc", ")");) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
argnames.push(as_symbol(AST_SymbolFunarg)); argnames.push(maybe_destructured(AST_SymbolFunarg));
} }
next(); next();
var loop = S.in_loop; 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 = []; var a = [];
for (;;) { for (;;) {
var start = S.token; var start = S.token;
var name = as_symbol(type); var name = maybe_destructured(type);
var value = null; var value = null;
if (is("operator", "=")) { if (is("operator", "=")) {
next(); next();
value = expression(false, no_in); 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"); croak("Missing initializer in declaration");
} }
a.push(new AST_VarDef({ a.push(new AST_VarDef({
@@ -1174,7 +1176,7 @@ function parse($TEXT, options) {
var const_ = function(no_in) { var const_ = function(no_in) {
return new AST_Const({ return new AST_Const({
start : prev(), start : prev(),
definitions : vardefs(AST_SymbolConst, no_in, true), definitions : vardefs(AST_SymbolConst, no_in),
end : prev() end : prev()
}); });
}; };
@@ -1306,7 +1308,8 @@ function parse($TEXT, options) {
unexpected(); 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 = []; var first = true, a = [];
while (!is("punc", closing)) { while (!is("punc", closing)) {
if (first) first = false; else expect(","); if (first) first = false; else expect(",");
@@ -1314,7 +1317,7 @@ function parse($TEXT, options) {
if (is("punc", ",") && allow_empty) { if (is("punc", ",") && allow_empty) {
a.push(new AST_Hole({ start: S.token, end: S.token })); a.push(new AST_Hole({ start: S.token, end: S.token }));
} else { } else {
a.push(expression(false)); a.push(parser());
} }
} }
next(); next();
@@ -1449,6 +1452,54 @@ function parse($TEXT, options) {
return sym; 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) { function mark_pure(call) {
var start = call.start; var start = call.start;
var comments = start.comments_before; var comments = start.comments_before;
@@ -1578,11 +1629,43 @@ function parse($TEXT, options) {
return expr instanceof AST_PropAccess || expr instanceof AST_SymbolRef; 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 maybe_assign = function(no_in) {
var start = S.token; var start = S.token;
var left = maybe_conditional(no_in), val = S.token.value; var left = maybe_conditional(no_in), val = S.token.value;
if (is("operator") && ASSIGNMENT[val]) { if (is("operator") && ASSIGNMENT[val]) {
if (is_assignable(left)) { if (is_assignable(left) || val == "=" && (left = to_destructured(left)) instanceof AST_Destructured) {
next(); next();
return new AST_Assign({ return new AST_Assign({
start : start, start : start,

View File

@@ -204,7 +204,17 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
// pass 2: find back references and eval // pass 2: find back references and eval
self.globals = new Dictionary(); self.globals = new Dictionary();
var in_arg = [];
var tw = new TreeWalker(function(node) { 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 instanceof AST_LoopControl) {
if (node.label) node.label.thedef.references.push(node); if (node.label) node.label.thedef.references.push(node);
return true; return true;
@@ -212,6 +222,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
var name = node.name; var name = node.name;
var sym = node.scope.find_variable(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) { if (!sym) {
sym = self.def_global(node); sym = self.def_global(node);
} else if (name == "arguments" && sym.scope instanceof AST_Lambda) { } else if (name == "arguments" && sym.scope instanceof AST_Lambda) {

View File

@@ -160,6 +160,16 @@ TreeTransformer.prototype = new TreeWalker;
DEF(AST_Array, function(self, tw) { DEF(AST_Array, function(self, tw) {
self.elements = do_list(self.elements, 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) { DEF(AST_Object, function(self, tw) {
self.properties = do_list(self.properties, tw); self.properties = do_list(self.properties, tw);
}); });

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -377,6 +377,118 @@ function createArgs(recurmax, stmtDepth, canThrow) {
return args.join(", "); 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) { function filterDirective(s) {
if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ";" + s[2]; if (!generate_directive && !s[1] && /\("/.test(s[2])) s[2] = ";" + s[2];
return s; return s;
@@ -415,11 +527,37 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
return names.indexOf(name) < 0; return names.indexOf(name) < 0;
}); });
var len = VAR_NAMES.length; var len = VAR_NAMES.length;
var s = type + " " + names.map(function(name) { var s = type + " ";
var value = createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); switch (rng(10)) {
VAR_NAMES.push(name); case 0:
return name + " = " + value; while (!rng(10)) names.splice(rng(names.length + 1), 0, "");
}).join(", ") + ";"; 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)); VAR_NAMES = save.concat(VAR_NAMES.slice(len));
return s; return s;
} }
@@ -429,9 +567,9 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
if (--recurmax < 0) { return ";"; } if (--recurmax < 0) { return ";"; }
if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0;
var s = []; var s = [];
var name; var name, args;
var varNames = VAR_NAMES.slice();
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
var namesLenBefore = VAR_NAMES.length;
if (allowDefun || rng(5) > 0) { if (allowDefun || rng(5) > 0) {
name = "f" + funcs++; name = "f" + funcs++;
} else { } else {
@@ -439,7 +577,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
name = createVarName(MANDATORY, !allowDefun); name = createVarName(MANDATORY, !allowDefun);
unique_vars.length -= 3; 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()); s.push(defns());
if (rng(5) === 0) { if (rng(5) === 0) {
// functions with functions. lower the recursion to prevent a mess. // functions with functions. lower the recursion to prevent a mess.
@@ -450,17 +597,16 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
} }
s.push("}", ""); s.push("}", "");
s = filterDirective(s).join("\n"); s = filterDirective(s).join("\n");
VAR_NAMES.length = namesLenBefore;
}); });
VAR_NAMES = varNames;
if (!allowDefun) { if (!allowDefun) {
// avoid "function statements" (decl inside statements) // avoid "function statements" (decl inside statements)
s = "var " + createVarName(MANDATORY) + " = " + s; s = "var " + createVarName(MANDATORY) + " = " + s;
s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
} else if (!(name in called) || rng(3) > 0) { } else if (!(name in called) || args || rng(3)) {
s += "var " + createVarName(MANDATORY) + " = " + name; s += "var " + createVarName(MANDATORY) + " = " + name;
s += "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; s += "(" + (args || createArgs(recurmax, stmtDepth, canThrow)) + ")";
} }
return s + ";"; return s + ";";
@@ -561,8 +707,9 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
return [ return [
"{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ", "{var expr" + loop + " = " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ",
label.target + " for (", label.target + " for (",
/^key/.test(key) ? "var " : "", !/^key/.test(key) ? rng(10) ? "" : "var " : rng(10) ? "var " : rng(2) ? "let " : "const ",
key + " in expr" + loop + ") {", rng(20) ? key : "{ length: " + key + " }",
" in expr" + loop + ") {",
rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "", rng(5) > 1 ? "c = 1 + c; var " + createVarName(MANDATORY) + " = expr" + loop + "[" + key + "]; " : "",
createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth), 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 // note: default does not _need_ to be last
return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}"; return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}";
case STMT_VAR: 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: case 0:
unique_vars.push("c"); unique_vars.push("c");
var name = createVarName(MANDATORY); var name = createVarName(MANDATORY);
@@ -719,7 +871,28 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
return getVarName(); return getVarName();
case p++: 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++: case p++:
return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow);
case p++: case p++:
@@ -864,7 +1037,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
case p++: 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; called[name] = true;
return "typeof " + name + ' == "function" && --_calls_ >= 0 && ' + name + "(" + createArgs(recurmax, stmtDepth, canThrow) + ")"; 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) + ")"; return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case 3: case 3:
assignee = getVarName(); assignee = getVarName();
expr = "(" + assignee + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) switch (rng(20)) {
+ "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; 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 + ")"; return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
case 4: case 4:
assignee = getVarName(); assignee = getVarName();
expr = "(" + assignee + "." + getDotKey(true) + createAssignment() switch (rng(20)) {
+ _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; 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 + ")"; return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")";
default: default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow); 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 original_code, original_result, errored;
var uglify_code, uglify_result, ok; var uglify_code, uglify_result, ok;
for (var round = 1; round <= num_iterations; round++) { for (var round = 1; round <= num_iterations; round++) {