support class literals (#4658)

This commit is contained in:
Alex Lam S.L 2021-02-23 14:55:08 +00:00 committed by GitHub
parent e535f19189
commit d68d155f93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1701 additions and 175 deletions

View File

@ -217,6 +217,12 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)" $documentation: "The empty statement (empty block or simply a semicolon)"
}, AST_Statement); }, AST_Statement);
function is_statement(node) {
return node instanceof AST_Statement
&& !(node instanceof AST_ClassExpression)
&& !(node instanceof AST_LambdaExpression);
}
function validate_expression(value, prop, multiple, allow_spread, allow_hole) { function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
multiple = multiple ? "contain" : "be"; multiple = multiple ? "contain" : "be";
if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node"); if (!(value instanceof AST_Node)) throw new Error(prop + " must " + multiple + " AST_Node");
@ -224,9 +230,7 @@ function validate_expression(value, prop, multiple, allow_spread, allow_hole) {
if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured"); if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured");
if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole"); if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole");
if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread"); if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread");
if (value instanceof AST_Statement && !(value instanceof AST_LambdaExpression)) { if (is_statement(value)) throw new Error(prop + " cannot " + multiple + " AST_Statement");
throw new Error(prop + " cannot " + multiple + " AST_Statement");
}
if (value instanceof AST_SymbolDeclaration) { if (value instanceof AST_SymbolDeclaration) {
throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration"); throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration");
} }
@ -301,8 +305,7 @@ var AST_Block = DEFNODE("Block", "body", {
_validate: function() { _validate: function() {
if (this.TYPE == "Block") throw new Error("should not instantiate AST_Block"); if (this.TYPE == "Block") throw new Error("should not instantiate AST_Block");
this.body.forEach(function(node) { this.body.forEach(function(node) {
if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]"); if (!is_statement(node)) throw new Error("body must contain AST_Statement");
if (node instanceof AST_LambdaExpression) throw new Error("body cannot contain AST_LambdaExpression");
}); });
}, },
}, AST_BlockScope); }, AST_BlockScope);
@ -318,8 +321,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", {
}, },
_validate: function() { _validate: function() {
if (this.TYPE == "StatementWithBody") throw new Error("should not instantiate AST_StatementWithBody"); if (this.TYPE == "StatementWithBody") throw new Error("should not instantiate AST_StatementWithBody");
if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement"); if (!is_statement(this.body)) throw new Error("body must be AST_Statement");
if (this.body instanceof AST_LambdaExpression) throw new Error("body cannot be AST_LambdaExpression");
}, },
}, AST_BlockScope); }, AST_BlockScope);
@ -416,8 +418,7 @@ var AST_For = DEFNODE("For", "init condition step", {
_validate: function() { _validate: function() {
if (this.init != null) { if (this.init != null) {
if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node"); if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node");
if (this.init instanceof AST_Statement if (is_statement(this.init) && !(this.init instanceof AST_Definitions)) {
&& !(this.init instanceof AST_Definitions || this.init instanceof AST_LambdaExpression)) {
throw new Error("init cannot be AST_Statement"); throw new Error("init cannot be AST_Statement");
} }
} }
@ -699,7 +700,7 @@ var AST_AsyncArrow = DEFNODE("AsyncArrow", "value", {
var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", { var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", {
$documentation: "An asynchronous function expression", $documentation: "An asynchronous function expression",
$propdoc: { $propdoc: {
name: "[AST_SymbolLambda?] the name of this function", name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
}, },
_validate: function() { _validate: function() {
if (this.name != null) { if (this.name != null) {
@ -711,7 +712,7 @@ var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", {
var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", { var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", {
$documentation: "An asynchronous generator function expression", $documentation: "An asynchronous generator function expression",
$propdoc: { $propdoc: {
name: "[AST_SymbolLambda?] the name of this function", name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
}, },
_validate: function() { _validate: function() {
if (this.name != null) { if (this.name != null) {
@ -723,7 +724,7 @@ var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", {
var AST_Function = DEFNODE("Function", "name", { var AST_Function = DEFNODE("Function", "name", {
$documentation: "A function expression", $documentation: "A function expression",
$propdoc: { $propdoc: {
name: "[AST_SymbolLambda?] the name of this function", name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
}, },
_validate: function() { _validate: function() {
if (this.name != null) { if (this.name != null) {
@ -735,7 +736,7 @@ var AST_Function = DEFNODE("Function", "name", {
var AST_GeneratorFunction = DEFNODE("GeneratorFunction", "name", { var AST_GeneratorFunction = DEFNODE("GeneratorFunction", "name", {
$documentation: "A generator function expression", $documentation: "A generator function expression",
$propdoc: { $propdoc: {
name: "[AST_SymbolLambda?] the name of this function", name: "[AST_SymbolLambda?] the name of this function, or null if not specified",
}, },
_validate: function() { _validate: function() {
if (this.name != null) { if (this.name != null) {
@ -772,6 +773,111 @@ var AST_GeneratorDefun = DEFNODE("GeneratorDefun", null, {
$documentation: "A generator function definition", $documentation: "A generator function definition",
}, AST_LambdaDefinition); }, AST_LambdaDefinition);
/* -----[ classes ]----- */
var AST_Class = DEFNODE("Class", "extends name properties", {
$documentation: "Base class for class literals",
$propdoc: {
extends: "[AST_Node?] the super class, or null if not specified",
properties: "[AST_ClassProperty*] array of class properties",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
if (node.name) node.name.walk(visitor);
if (node.extends) node.extends.walk(visitor);
node.properties.forEach(function(prop) {
prop.walk(visitor);
});
});
},
_validate: function() {
if (this.TYPE == "Class") throw new Error("should not instantiate AST_Class");
if (this.extends != null) must_be_expression(this, "extends");
this.properties.forEach(function(node) {
if (!(node instanceof AST_ClassProperty)) throw new Error("properties must contain AST_ClassProperty");
});
},
}, AST_BlockScope);
var AST_DefClass = DEFNODE("DefClass", null, {
$documentation: "A class definition",
$propdoc: {
name: "[AST_SymbolDefClass] the name of this class",
},
_validate: function() {
if (!(this.name instanceof AST_SymbolDefClass)) throw new Error("name must be AST_SymbolDefClass");
},
}, AST_Class);
var AST_ClassExpression = DEFNODE("ClassExpression", null, {
$documentation: "A class expression",
$propdoc: {
name: "[AST_SymbolClass?] the name of this class, or null if not specified",
},
_validate: function() {
if (this.name != null) {
if (!(this.name instanceof AST_SymbolClass)) throw new Error("name must be AST_SymbolClass");
}
},
}, AST_Class);
var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
$documentation: "Base class for `class` properties",
$propdoc: {
key: "[string|AST_Node] property name (AST_Node for computed property)",
private: "[boolean] whether this is a private property",
static: "[boolean] whether this is a static property",
value: "[AST_Node?] property value (AST_Accessor for getters/setters, AST_LambdaExpression for methods, null if not specified for fields)",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
if (node.key instanceof AST_Node) node.key.walk(visitor);
if (node.value) node.value.walk(visitor);
});
},
_validate: function() {
if (this.TYPE == "ClassProperty") throw new Error("should not instantiate AST_ClassProperty");
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");
}
if(this.value != null) {
if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
}
},
});
var AST_ClassField = DEFNODE("ClassField", null, {
$documentation: "A `class` field",
_validate: function() {
if(this.value != null) must_be_expression(this, "value");
},
}, AST_ClassProperty);
var AST_ClassGetter = DEFNODE("ClassGetter", null, {
$documentation: "A `class` getter",
_validate: function() {
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ClassProperty);
var AST_ClassSetter = DEFNODE("ClassSetter", null, {
$documentation: "A `class` setter",
_validate: function() {
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ClassProperty);
var AST_ClassMethod = DEFNODE("ClassMethod", null, {
$documentation: "A `class` method",
_validate: function() {
if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
if (this.value.name != null) throw new Error("name of class method's lambda must be null");
},
}, AST_ClassProperty);
/* -----[ JUMPS ]----- */ /* -----[ JUMPS ]----- */
var AST_Jump = DEFNODE("Jump", null, { var AST_Jump = DEFNODE("Jump", null, {
@ -857,8 +963,7 @@ var AST_If = DEFNODE("If", "condition alternative", {
_validate: function() { _validate: function() {
must_be_expression(this, "condition"); must_be_expression(this, "condition");
if (this.alternative != null) { if (this.alternative != null) {
if (!(this.alternative instanceof AST_Statement)) throw new Error("alternative must be AST_Statement"); if (!is_statement(this.alternative)) throw new Error("alternative must be AST_Statement");
if (this.alternative instanceof AST_LambdaExpression) throw new error("alternative cannot be AST_LambdaExpression");
} }
}, },
}, AST_StatementWithBody); }, AST_StatementWithBody);
@ -1042,7 +1147,7 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", { var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", {
$documentation: "An `export` statement", $documentation: "An `export` statement",
$propdoc: { $propdoc: {
body: "[AST_Definitions|AST_LambdaDefinition] the statement to export", body: "[AST_DefClass|AST_Definitions|AST_LambdaDefinition] the statement to export",
}, },
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
@ -1051,8 +1156,10 @@ var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", {
}); });
}, },
_validate: function() { _validate: function() {
if (!(this.body instanceof AST_Definitions || this.body instanceof AST_LambdaDefinition)) { if (!(this.body instanceof AST_DefClass
throw new Error("body must be AST_Definitions or AST_LambdaDefinition"); || this.body instanceof AST_Definitions
|| this.body instanceof AST_LambdaDefinition)) {
throw new Error("body must be AST_DefClass, AST_Definitions or AST_LambdaDefinition");
} }
}, },
}, AST_Statement); }, AST_Statement);
@ -1069,8 +1176,14 @@ var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
}); });
}, },
_validate: function() { _validate: function() {
if (this.body instanceof AST_Lambda && this.body.name) { if (this.body instanceof AST_Class && this.body.name) {
if (!(this.body instanceof AST_LambdaDefinition)) throw new Error("body must be AST_LambdaDefinition"); if (!(this.body instanceof AST_DefClass)) {
throw new Error("body must be AST_DefClass when named");
}
} else if (this.body instanceof AST_Lambda && this.body.name) {
if (!(this.body instanceof AST_LambdaDefinition)) {
throw new Error("body must be AST_LambdaDefinition when named");
}
} else { } else {
must_be_expression(this, "body"); must_be_expression(this, "body");
} }
@ -1574,6 +1687,13 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
}, },
}, AST_ObjectProperty); }, AST_ObjectProperty);
var AST_ObjectMethod = DEFNODE("ObjectMethod", null, {
$documentation: "A key(){} object property",
_validate: function() {
if (!(this.value instanceof AST_LambdaExpression)) throw new Error("value must be AST_LambdaExpression");
},
}, AST_ObjectKeyVal);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
$documentation: "An object setter property", $documentation: "An object setter property",
_validate: function() { _validate: function() {
@ -1609,6 +1729,16 @@ var AST_SymbolConst = DEFNODE("SymbolConst", null, {
$documentation: "Symbol defining a constant", $documentation: "Symbol defining a constant",
}, AST_SymbolDeclaration); }, AST_SymbolDeclaration);
var AST_SymbolImport = DEFNODE("SymbolImport", "key", {
$documentation: "Symbol defined by an `import` statement",
$propdoc: {
key: "[string] the original `export` name",
},
_validate: function() {
if (typeof this.key != "string") throw new Error("key must be string");
},
}, AST_SymbolConst);
var AST_SymbolLet = DEFNODE("SymbolLet", null, { var AST_SymbolLet = DEFNODE("SymbolLet", null, {
$documentation: "Symbol defining a lexical-scoped variable", $documentation: "Symbol defining a lexical-scoped variable",
}, AST_SymbolDeclaration); }, AST_SymbolDeclaration);
@ -1621,16 +1751,6 @@ var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, {
$documentation: "Symbol naming a function argument", $documentation: "Symbol naming a function argument",
}, AST_SymbolVar); }, AST_SymbolVar);
var AST_SymbolImport = DEFNODE("SymbolImport", "key", {
$documentation: "Symbol defined by an `import` statement",
$propdoc: {
key: "[string] the original `export` name",
},
_validate: function() {
if (typeof this.key != "string") throw new Error("key must be string");
},
}, AST_SymbolVar);
var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { var AST_SymbolDefun = DEFNODE("SymbolDefun", null, {
$documentation: "Symbol defining a function", $documentation: "Symbol defining a function",
}, AST_SymbolDeclaration); }, AST_SymbolDeclaration);
@ -1639,6 +1759,14 @@ var AST_SymbolLambda = DEFNODE("SymbolLambda", null, {
$documentation: "Symbol naming a function expression", $documentation: "Symbol naming a function expression",
}, AST_SymbolDeclaration); }, AST_SymbolDeclaration);
var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, {
$documentation: "Symbol defining a class",
}, AST_SymbolLet);
var AST_SymbolClass = DEFNODE("SymbolClass", null, {
$documentation: "Symbol naming a class expression",
}, AST_SymbolLet);
var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { var AST_SymbolCatch = DEFNODE("SymbolCatch", null, {
$documentation: "Symbol naming the exception in catch", $documentation: "Symbol naming the exception in catch",
}, AST_SymbolDeclaration); }, AST_SymbolDeclaration);
@ -1672,12 +1800,26 @@ var AST_LabelRef = DEFNODE("LabelRef", null, {
$documentation: "Reference to a label symbol", $documentation: "Reference to a label symbol",
}, AST_Symbol); }, AST_Symbol);
var AST_ObjectIdentity = DEFNODE("ObjectIdentity", null, {
$documentation: "Base class for `super` & `this`",
_validate: function() {
if (this.TYPE == "ObjectIdentity") throw new Error("should not instantiate AST_ObjectIdentity");
},
}, AST_Symbol);
var AST_Super = DEFNODE("Super", null, {
$documentation: "The `super` symbol",
_validate: function() {
if (this.name !== "super") throw new Error('name must be "super"');
},
}, AST_ObjectIdentity);
var AST_This = DEFNODE("This", null, { var AST_This = DEFNODE("This", null, {
$documentation: "The `this` symbol", $documentation: "The `this` symbol",
_validate: function() { _validate: function() {
if (this.name !== "this") throw new Error('name must be "this"'); if (this.name !== "this") throw new Error('name must be "this"');
}, },
}, AST_Symbol); }, AST_ObjectIdentity);
var AST_Template = DEFNODE("Template", "expressions strings tag", { var AST_Template = DEFNODE("Template", "expressions strings tag", {
$documentation: "A template literal, i.e. tag`str1${expr1}...strN${exprN}strN+1`", $documentation: "A template literal, i.e. tag`str1${expr1}...strN${exprN}strN+1`",
@ -1837,7 +1979,8 @@ TreeWalker.prototype = {
this.stack.push(node); this.stack.push(node);
}, },
pop: function() { pop: function() {
if (this.stack.pop() instanceof AST_Lambda) { var node = this.stack.pop();
if (node instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives); this.directives = Object.getPrototypeOf(this.directives);
} }
}, },

View File

@ -348,7 +348,7 @@ merge(Compressor.prototype, {
var props = obj.properties; var props = obj.properties;
for (var i = props.length; --i >= 0;) { for (var i = props.length; --i >= 0;) {
var prop = props[i]; var prop = props[i];
if (!(prop instanceof AST_ObjectKeyVal)) return; if (!can_hoist_property(prop)) return;
if (!value && props[i].key === key) value = props[i].value; if (!value && props[i].key === key) value = props[i].value;
} }
} }
@ -600,7 +600,7 @@ merge(Compressor.prototype, {
if (!value) return false; if (!value) return false;
return value.is_constant() return value.is_constant()
|| value instanceof AST_Lambda || value instanceof AST_Lambda
|| value instanceof AST_This; || value instanceof AST_ObjectIdentity;
} }
function has_escaped(d, node, parent) { function has_escaped(d, node, parent) {
@ -933,6 +933,36 @@ merge(Compressor.prototype, {
exp.left.definition().bool_fn++; exp.left.definition().bool_fn++;
} }
}); });
def(AST_Class, function(tw, descend, compressor) {
var node = this;
node.variables.each(function(def) {
reset_def(tw, compressor, def);
});
if (!node.name) return;
var d = node.name.definition();
if (safe_to_assign(tw, d, true)) {
mark(tw, d);
tw.loop_ids[d.id] = tw.in_loop;
d.fixed = function() {
return node;
};
d.fixed.assigns = [ node ];
if (!is_safe_lexical(d)) d.single_use = false;
} else {
d.fixed = false;
}
});
def(AST_ClassField, function(tw) {
var node = this;
if (node.static) return;
if (node.key instanceof AST_Node) node.key.walk(tw);
if (node.value) {
push(tw);
node.value.walk(tw);
pop(tw);
}
return true;
});
def(AST_Conditional, function(tw) { def(AST_Conditional, function(tw) {
this.condition.walk(tw); this.condition.walk(tw);
push(tw); push(tw);
@ -1374,12 +1404,7 @@ merge(Compressor.prototype, {
} }
function is_lhs_read_only(lhs, compressor) { function is_lhs_read_only(lhs, compressor) {
if (lhs instanceof AST_This) return true; if (lhs instanceof AST_ObjectIdentity) return true;
if (lhs instanceof AST_SymbolRef) {
if (lhs.is_immutable()) return true;
var def = lhs.definition();
return compressor.exposed(def) && identifier_atom[def.name];
}
if (lhs instanceof AST_PropAccess) { if (lhs instanceof AST_PropAccess) {
lhs = lhs.expression; lhs = lhs.expression;
if (lhs instanceof AST_SymbolRef) { if (lhs instanceof AST_SymbolRef) {
@ -1390,6 +1415,11 @@ merge(Compressor.prototype, {
if (lhs.is_constant()) return true; if (lhs.is_constant()) return true;
return is_lhs_read_only(lhs, compressor); return is_lhs_read_only(lhs, compressor);
} }
if (lhs instanceof AST_SymbolRef) {
if (lhs.is_immutable()) return true;
var def = lhs.definition();
return compressor.exposed(def) && identifier_atom[def.name];
}
return false; return false;
} }
@ -1472,10 +1502,14 @@ merge(Compressor.prototype, {
return array; return array;
} }
function is_lexical_definition(stat) {
return stat instanceof AST_Const || stat instanceof AST_DefClass || stat instanceof AST_Let;
}
function as_statement_array(thing) { function as_statement_array(thing) {
if (thing === null) return []; if (thing === null) return [];
if (thing instanceof AST_BlockStatement) return all(thing.body, function(stat) { if (thing instanceof AST_BlockStatement) return all(thing.body, function(stat) {
return !(stat instanceof AST_Const || stat instanceof AST_Let); return !is_lexical_definition(stat);
}) ? thing.body : [ thing ]; }) ? thing.body : [ thing ];
if (thing instanceof AST_EmptyStatement) return []; if (thing instanceof AST_EmptyStatement) return [];
if (thing instanceof AST_Statement) return [ thing ]; if (thing instanceof AST_Statement) return [ thing ];
@ -1851,7 +1885,12 @@ merge(Compressor.prototype, {
} }
function handle_custom_scan_order(node, tt) { function handle_custom_scan_order(node, tt) {
if (!(node instanceof AST_BlockScope)) return; if (!(node instanceof AST_BlockScope)) {
if (!(node instanceof AST_ClassProperty && !node.static)) return;
// Skip non-static class property values
if (node.key instanceof AST_Node) node.key = node.key.transform(tt);
return node;
}
// Skip (non-executed) functions // Skip (non-executed) functions
if (node instanceof AST_Scope) return node; if (node instanceof AST_Scope) return node;
// Stop upon collision with block-scoped variables // Stop upon collision with block-scoped variables
@ -1905,6 +1944,7 @@ merge(Compressor.prototype, {
if (!lhs.equivalent_to(node.expression)) return false; if (!lhs.equivalent_to(node.expression)) return false;
return !(rvalue instanceof AST_LambdaExpression && !rvalue.contains_this()); return !(rvalue instanceof AST_LambdaExpression && !rvalue.contains_this());
} }
if (node instanceof AST_Class) return !compressor.has_directive("use strict");
if (node instanceof AST_Debugger) return true; if (node instanceof AST_Debugger) return true;
if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name; if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name;
if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node; if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node;
@ -1990,6 +2030,7 @@ merge(Compressor.prototype, {
if (node instanceof AST_Function) { if (node instanceof AST_Function) {
return compressor.option("ie8") && node.name && lvalues.has(node.name.name); return compressor.option("ie8") && node.name && lvalues.has(node.name.name);
} }
if (node instanceof AST_ObjectIdentity) return symbol_in_lvalues(node, parent);
if (node instanceof AST_PropAccess) { if (node instanceof AST_PropAccess) {
var exp = node.expression; var exp = node.expression;
return side_effects || !value_def && exp.may_throw_on_access(compressor) return side_effects || !value_def && exp.may_throw_on_access(compressor)
@ -2003,7 +2044,6 @@ merge(Compressor.prototype, {
return (in_try || def.scope.resolve() !== scope) && !can_drop_symbol(node); return (in_try || def.scope.resolve() !== scope) && !can_drop_symbol(node);
} }
if (node instanceof AST_Template) return node.tag && !is_raw_tag(compressor, node.tag); if (node instanceof AST_Template) return node.tag && !is_raw_tag(compressor, node.tag);
if (node instanceof AST_This) return symbol_in_lvalues(node, parent);
if (node instanceof AST_VarDef) { if (node instanceof AST_VarDef) {
if (check_destructured(node.name)) return true; if (check_destructured(node.name)) return true;
return (node.value || parent instanceof AST_Let) && node.name.match_symbol(function(node) { return (node.value || parent instanceof AST_Let) && node.name.match_symbol(function(node) {
@ -2079,7 +2119,7 @@ merge(Compressor.prototype, {
} }
arg = null; arg = null;
} }
if (node instanceof AST_This && (fn_strict || !tw.find_parent(AST_Scope))) { if (node instanceof AST_ObjectIdentity && (fn_strict || !tw.find_parent(AST_Scope))) {
arg = null; arg = null;
return true; return true;
} }
@ -2473,12 +2513,12 @@ merge(Compressor.prototype, {
expr.left.walk(marker); expr.left.walk(marker);
expr = expr.right; expr = expr.right;
} }
if (expr instanceof AST_ObjectIdentity) return rhs_exact_match;
if (expr instanceof AST_SymbolRef) { if (expr instanceof AST_SymbolRef) {
var value = expr.evaluate(compressor); var value = expr.evaluate(compressor);
if (value === expr) return rhs_exact_match; if (value === expr) return rhs_exact_match;
return rhs_fuzzy_match(value, rhs_exact_match); return rhs_fuzzy_match(value, rhs_exact_match);
} }
if (expr instanceof AST_This) return rhs_exact_match;
if (expr.is_truthy()) return rhs_fuzzy_match(true, return_false); if (expr.is_truthy()) return rhs_fuzzy_match(true, return_false);
if (expr.is_constant()) { if (expr.is_constant()) {
var ev = expr.evaluate(compressor); var ev = expr.evaluate(compressor);
@ -2519,7 +2559,7 @@ merge(Compressor.prototype, {
if (!node) return true; if (!node) return true;
} }
if (node instanceof AST_Assign) return node.operator == "=" && may_be_global(node.right); if (node instanceof AST_Assign) return node.operator == "=" && may_be_global(node.right);
return node instanceof AST_PropAccess || node instanceof AST_This; return node instanceof AST_PropAccess || node instanceof AST_ObjectIdentity;
} }
function get_lvalues(expr) { function get_lvalues(expr) {
@ -2531,7 +2571,7 @@ merge(Compressor.prototype, {
var value; var value;
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
value = node.fixed_value() || node; value = node.fixed_value() || node;
} else if (node instanceof AST_This) { } else if (node instanceof AST_ObjectIdentity) {
value = node; value = node;
} }
if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0));
@ -2681,7 +2721,7 @@ merge(Compressor.prototype, {
var stat = statements[i]; var stat = statements[i];
if (stat instanceof AST_BlockStatement) { if (stat instanceof AST_BlockStatement) {
if (all(stat.body, function(stat) { if (all(stat.body, function(stat) {
return !(stat instanceof AST_Const || stat instanceof AST_Let); return !is_lexical_definition(stat);
})) { })) {
CHANGED = true; CHANGED = true;
eliminate_spurious_blocks(stat.body); eliminate_spurious_blocks(stat.body);
@ -3025,7 +3065,7 @@ merge(Compressor.prototype, {
var line = block.body[i]; var line = block.body[i];
if (line instanceof AST_Var && declarations_only(line)) { if (line instanceof AST_Var && declarations_only(line)) {
decls.push(line); decls.push(line);
} else if (stat || line instanceof AST_Const || line instanceof AST_Let) { } else if (stat || is_lexical_definition(line)) {
return false; return false;
} else { } else {
stat = line; stat = line;
@ -3387,7 +3427,7 @@ merge(Compressor.prototype, {
function push(node) { function push(node) {
if (block) { if (block) {
block.push(node); block.push(node);
if (node instanceof AST_Const || node instanceof AST_Let) block.required = true; if (is_lexical_definition(node)) block.required = true;
} else { } else {
target.push(node); target.push(node);
} }
@ -3531,6 +3571,9 @@ merge(Compressor.prototype, {
return prop instanceof AST_ObjectKeyVal; return prop instanceof AST_ObjectKeyVal;
}); });
}); });
def(AST_ObjectIdentity, function(compressor) {
return is_strict(compressor) && !this.scope.new;
});
def(AST_Sequence, function(compressor) { def(AST_Sequence, function(compressor) {
return this.tail_node()._dot_throw(compressor); return this.tail_node()._dot_throw(compressor);
}); });
@ -3553,9 +3596,6 @@ merge(Compressor.prototype, {
this._dot_throw = return_false; this._dot_throw = return_false;
return false; return false;
}); });
def(AST_This, function(compressor) {
return is_strict(compressor) && !this.scope.new;
});
def(AST_UnaryPrefix, function() { def(AST_UnaryPrefix, function() {
return this.operator == "void"; return this.operator == "void";
}); });
@ -4077,6 +4117,7 @@ merge(Compressor.prototype, {
}); });
def(AST_Accessor, return_this); def(AST_Accessor, return_this);
def(AST_BigInt, return_this); def(AST_BigInt, return_this);
def(AST_Class, return_this);
def(AST_Node, return_this); def(AST_Node, return_this);
def(AST_Constant, function() { def(AST_Constant, function() {
return this.value; return this.value;
@ -4595,6 +4636,9 @@ merge(Compressor.prototype, {
} }
return basic_negation(this); return basic_negation(this);
}); });
def(AST_ClassExpression, function() {
return basic_negation(this);
});
def(AST_Conditional, function(compressor, first_in_statement) { def(AST_Conditional, function(compressor, first_in_statement) {
var self = this.clone(); var self = this.clone();
self.consequent = self.consequent.negate(compressor); self.consequent = self.consequent.negate(compressor);
@ -4673,7 +4717,7 @@ merge(Compressor.prototype, {
|| exp instanceof AST_Object && all(exp.properties, function(prop) { || exp instanceof AST_Object && all(exp.properties, function(prop) {
return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread);
}) })
|| exp instanceof AST_This || exp instanceof AST_ObjectIdentity
|| exp instanceof AST_Unary); || exp instanceof AST_Unary);
} }
@ -4697,7 +4741,7 @@ merge(Compressor.prototype, {
var lhs = this.left; var lhs = this.left;
if (!(lhs instanceof AST_PropAccess)) return true; if (!(lhs instanceof AST_PropAccess)) return true;
var node = lhs.expression; var node = lhs.expression;
return !(node instanceof AST_This) return !(node instanceof AST_ObjectIdentity)
|| !node.scope.new || !node.scope.new
|| lhs instanceof AST_Sub && lhs.property.has_side_effects(compressor) || lhs instanceof AST_Sub && lhs.property.has_side_effects(compressor)
|| this.right.has_side_effects(compressor); || this.right.has_side_effects(compressor);
@ -4721,6 +4765,13 @@ merge(Compressor.prototype, {
return this.expression.has_side_effects(compressor) return this.expression.has_side_effects(compressor)
|| any(this.body, compressor); || any(this.body, compressor);
}); });
def(AST_Class, function(compressor) {
return this.extends || any(this.properties, compressor);
});
def(AST_ClassProperty, function(compressor) {
return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
|| this.static && this.value && this.value.has_side_effects(compressor);
});
def(AST_Conditional, function(compressor) { def(AST_Conditional, function(compressor) {
return this.condition.has_side_effects(compressor) return this.condition.has_side_effects(compressor)
|| this.consequent.has_side_effects(compressor) || this.consequent.has_side_effects(compressor)
@ -4760,6 +4811,7 @@ merge(Compressor.prototype, {
return spread_side_effects(exp) || exp.has_side_effects(compressor); return spread_side_effects(exp) || exp.has_side_effects(compressor);
}); });
}); });
def(AST_ObjectIdentity, return_false);
def(AST_ObjectProperty, function(compressor) { def(AST_ObjectProperty, function(compressor) {
return this.key instanceof AST_Node && this.key.has_side_effects(compressor) return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
|| this.value.has_side_effects(compressor); || this.value.has_side_effects(compressor);
@ -4786,7 +4838,6 @@ merge(Compressor.prototype, {
def(AST_Template, function(compressor) { def(AST_Template, function(compressor) {
return this.tag && !is_raw_tag(compressor, this.tag) || any(this.expressions, compressor); return this.tag && !is_raw_tag(compressor, this.tag) || any(this.expressions, compressor);
}); });
def(AST_This, return_false);
def(AST_Try, function(compressor) { def(AST_Try, function(compressor) {
return any(this.body, compressor) return any(this.body, compressor)
|| this.bcatch && this.bcatch.has_side_effects(compressor) || this.bcatch && this.bcatch.has_side_effects(compressor)
@ -4810,8 +4861,8 @@ merge(Compressor.prototype, {
def(AST_Constant, return_false); def(AST_Constant, return_false);
def(AST_EmptyStatement, return_false); def(AST_EmptyStatement, return_false);
def(AST_Lambda, return_false); def(AST_Lambda, return_false);
def(AST_ObjectIdentity, return_false);
def(AST_SymbolDeclaration, return_false); def(AST_SymbolDeclaration, return_false);
def(AST_This, return_false);
function any(list, compressor) { function any(list, compressor) {
for (var i = list.length; --i >= 0;) for (var i = list.length; --i >= 0;)
@ -4933,6 +4984,12 @@ merge(Compressor.prototype, {
&& this.right.is_constant_expression() && this.right.is_constant_expression()
&& (this.operator != "in" || is_object(this.right)); && (this.operator != "in" || is_object(this.right));
}); });
def(AST_Class, function() {
return !this.extends && all(this.properties);
});
def(AST_ClassProperty, function() {
return typeof this.key == "string" && (!this.value || this.value.is_constant_expression());
});
def(AST_Constant, return_true); def(AST_Constant, return_true);
def(AST_Lambda, function(scope) { def(AST_Lambda, function(scope) {
var self = this; var self = this;
@ -4965,7 +5022,7 @@ merge(Compressor.prototype, {
result = false; result = false;
return true; return true;
} }
if (node instanceof AST_This) { if (node instanceof AST_ObjectIdentity) {
if (scopes.length == 0 && is_arrow(self)) result = false; if (scopes.length == 0 && is_arrow(self)) result = false;
return true; return true;
} }
@ -5042,7 +5099,7 @@ merge(Compressor.prototype, {
return in_list ? List.skip : make_node(AST_EmptyStatement, node); return in_list ? List.skip : make_node(AST_EmptyStatement, node);
case 1: case 1:
var stat = node.body[0]; var stat = node.body[0];
if (stat instanceof AST_Const || stat instanceof AST_Let) return node; if (is_lexical_definition(stat)) return node;
if (parent instanceof AST_IterationStatement && stat instanceof AST_LambdaDefinition) return node; if (parent instanceof AST_IterationStatement && stat instanceof AST_LambdaDefinition) return node;
return stat; return stat;
} }
@ -5347,6 +5404,10 @@ merge(Compressor.prototype, {
}); });
return true; return true;
} }
if (node instanceof AST_SymbolConst || node instanceof AST_SymbolLet) {
references[node.definition().id] = false;
return true;
}
if (node instanceof AST_SymbolRef) { if (node instanceof AST_SymbolRef) {
mark(node, true); mark(node, true);
return true; return true;
@ -5549,6 +5610,12 @@ merge(Compressor.prototype, {
} }
} }
function to_class_expr(defcl, drop_name) {
var cl = make_node(AST_ClassExpression, defcl, defcl);
cl.name = drop_name ? null : make_node(AST_SymbolClass, defcl.name, defcl.name);
return cl;
}
function to_func_expr(defun, drop_name) { function to_func_expr(defun, drop_name) {
var ctor; var ctor;
switch (defun.CTOR) { switch (defun.CTOR) {
@ -5658,6 +5725,25 @@ merge(Compressor.prototype, {
} }
if (node === self) return; if (node === self) return;
if (scope === self) { if (scope === self) {
if (node instanceof AST_DefClass) {
var def = node.name.definition();
if ((!drop_funcs || def.exported) && !(def.id in in_use_ids)) {
in_use_ids[def.id] = true;
in_use.push(def);
}
if (node.extends) node.extends.walk(tw);
var is_export = tw.parent() instanceof AST_ExportDefault;
node.properties.forEach(function(prop) {
if (prop.key instanceof AST_Node) prop.key.walk(tw);
if (!prop.value) return;
if (is_export || prop instanceof AST_ClassField && prop.static) {
prop.value.walk(tw);
} else {
initializations.add(def.id, prop.value);
}
});
return true;
}
if (node instanceof AST_LambdaDefinition) { if (node instanceof AST_LambdaDefinition) {
var def = node.name.definition(); var def = node.name.definition();
if ((!drop_funcs || def.exported) && !(def.id in in_use_ids)) { if ((!drop_funcs || def.exported) && !(def.id in in_use_ids)) {
@ -5855,13 +5941,32 @@ merge(Compressor.prototype, {
} }
if (node instanceof AST_Call) calls_to_drop_args.push(node); if (node instanceof AST_Call) calls_to_drop_args.push(node);
if (scope !== self) return; if (scope !== self) return;
if (drop_funcs && node !== self && node instanceof AST_DefClass) {
var def = node.name.definition();
if (!(def.id in in_use_ids)) {
log(node.name, "Dropping unused class {name}");
def.eliminated++;
descend(node, tt);
if (parent instanceof AST_ExportDefault) return to_class_expr(node, true);
var trimmed = node.drop_side_effect_free(compressor, true);
if (trimmed === node) trimmed = to_class_expr(node, true);
if (trimmed) return make_node(AST_SimpleStatement, node, { body: trimmed });
return in_list ? List.skip : make_node(AST_EmptyStatement, node);
}
}
if (node instanceof AST_ClassExpression && node.name && drop_fn_name(node.name.definition())) {
unused_fn_names.push(node);
}
if (node instanceof AST_Lambda) { if (node instanceof AST_Lambda) {
if (drop_funcs && node !== self && node instanceof AST_LambdaDefinition) { if (drop_funcs && node !== self && node instanceof AST_LambdaDefinition) {
var def = node.name.definition(); var def = node.name.definition();
if (!(def.id in in_use_ids)) { if (!(def.id in in_use_ids)) {
log(node.name, "Dropping unused function {name}"); log(node.name, "Dropping unused function {name}");
def.eliminated++; def.eliminated++;
if (parent instanceof AST_ExportDefault) return to_func_expr(node, true); if (parent instanceof AST_ExportDefault) {
descend_scope();
return to_func_expr(node, true);
}
return in_list ? List.skip : make_node(AST_EmptyStatement, node); return in_list ? List.skip : make_node(AST_EmptyStatement, node);
} }
} }
@ -6151,16 +6256,20 @@ merge(Compressor.prototype, {
return node; return node;
} }
if (node instanceof AST_Scope) { if (node instanceof AST_Scope) {
var save_scope = scope; descend_scope();
scope = node;
descend(node, tt);
scope = save_scope;
return node; return node;
} }
if (node instanceof AST_SymbolImport) { if (node instanceof AST_SymbolImport) {
if (!compressor.option("imports") || node.definition().id in in_use_ids) return node; if (!compressor.option("imports") || node.definition().id in in_use_ids) return node;
return in_list ? List.skip : null; return in_list ? List.skip : null;
} }
function descend_scope() {
var save_scope = scope;
scope = node;
descend(node, tt);
scope = save_scope;
}
}, function(node, in_list) { }, function(node, in_list) {
if (node instanceof AST_BlockStatement) { if (node instanceof AST_BlockStatement) {
return trim_block(node, tt.parent(), in_list); return trim_block(node, tt.parent(), in_list);
@ -6740,6 +6849,7 @@ merge(Compressor.prototype, {
if (!compressor.option("booleans")) return; if (!compressor.option("booleans")) return;
var bool_returns = map_bool_returns(this); var bool_returns = map_bool_returns(this);
if (!all_bool(this.name.definition(), bool_returns, compressor)) return; if (!all_bool(this.name.definition(), bool_returns, compressor)) return;
if (compressor.parent() instanceof AST_ExportDefault) return;
process_boolean_returns(this, compressor); process_boolean_returns(this, compressor);
}); });
AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) { AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) {
@ -6915,9 +7025,7 @@ merge(Compressor.prototype, {
if (sym.fixed_value() !== right) return; if (sym.fixed_value() !== right) return;
return right instanceof AST_Object return right instanceof AST_Object
&& right.properties.length > 0 && right.properties.length > 0
&& all(right.properties, function(prop) { && all(right.properties, can_hoist_property)
return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string";
})
&& all(def.references, function(ref) { && all(def.references, function(ref) {
return ref.fixed_value() === right; return ref.fixed_value() === right;
}) })
@ -6943,7 +7051,6 @@ merge(Compressor.prototype, {
// returned if nothing changed. // returned if nothing changed.
function trim(nodes, compressor, first_in_statement, spread) { function trim(nodes, compressor, first_in_statement, spread) {
var len = nodes.length; var len = nodes.length;
if (!len) return null;
var ret = [], changed = false; var ret = [], changed = false;
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
var node = nodes[i]; var node = nodes[i];
@ -6959,7 +7066,7 @@ merge(Compressor.prototype, {
first_in_statement = false; first_in_statement = false;
} }
} }
return changed ? ret.length ? ret : null : nodes; return ret.length ? changed ? ret : nodes : null;
} }
function array_spread(node, compressor, first_in_statement) { function array_spread(node, compressor, first_in_statement) {
var exp = node.expression; var exp = node.expression;
@ -7106,6 +7213,33 @@ merge(Compressor.prototype, {
} }
return self; return self;
}); });
def(AST_Class, function(compressor, first_in_statement) {
if (this.extends) return this;
var exprs = [], values = [];
this.properties.forEach(function(prop) {
if (prop.key instanceof AST_Node) exprs.push(prop.key);
if (prop instanceof AST_ClassField && prop.static && prop.value) values.push(prop.value);
});
exprs = trim(exprs, compressor, first_in_statement);
values = trim(values, compressor, first_in_statement);
if (!exprs) {
if (!values) return null;
exprs = [];
}
if (values) {
var fn = make_node(AST_Arrow, this, {
argnames: [],
body: [],
value: make_sequence(this, values),
});
fn.init_vars(this.parent_scope);
exprs.push(make_node(AST_Call, this, {
args: [],
expression: fn,
}));
}
return make_sequence(this, exprs);
});
def(AST_Conditional, function(compressor) { def(AST_Conditional, function(compressor) {
var consequent = this.consequent.drop_side_effect_free(compressor); var consequent = this.consequent.drop_side_effect_free(compressor);
var alternative = this.alternative.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor);
@ -7178,6 +7312,7 @@ merge(Compressor.prototype, {
}) : node; }) : node;
})); }));
}); });
def(AST_ObjectIdentity, return_null);
def(AST_Sequence, function(compressor, first_in_statement) { def(AST_Sequence, function(compressor, first_in_statement) {
var expressions = trim(this.expressions, compressor, first_in_statement); var expressions = trim(this.expressions, compressor, first_in_statement);
if (!expressions) return null; if (!expressions) return null;
@ -7220,7 +7355,6 @@ merge(Compressor.prototype, {
if (expressions.length == 0) return null; if (expressions.length == 0) return null;
return make_sequence(this, expressions).drop_side_effect_free(compressor, first_in_statement); return make_sequence(this, expressions).drop_side_effect_free(compressor, first_in_statement);
}); });
def(AST_This, return_null);
def(AST_Unary, function(compressor, first_in_statement) { def(AST_Unary, function(compressor, first_in_statement) {
var exp = this.expression; var exp = this.expression;
if (unary_side_effects[this.operator]) { if (unary_side_effects[this.operator]) {
@ -7485,7 +7619,7 @@ merge(Compressor.prototype, {
}); });
OPT(AST_ForEnumeration, function(self, compressor) { OPT(AST_ForEnumeration, function(self, compressor) {
if (compressor.option("varify") && (self.init instanceof AST_Const || self.init instanceof AST_Let)) { if (compressor.option("varify") && is_lexical_definition(self.init)) {
var name = self.init.definitions[0].name; var name = self.init.definitions[0].name;
if ((name instanceof AST_Destructured || name instanceof AST_SymbolLet) if ((name instanceof AST_Destructured || name instanceof AST_SymbolLet)
&& !name.match_symbol(function(node) { && !name.match_symbol(function(node) {
@ -10009,6 +10143,7 @@ merge(Compressor.prototype, {
def.single_use = false; def.single_use = false;
fixed._squeezed = true; fixed._squeezed = true;
fixed.single_use = true; fixed.single_use = true;
if (fixed instanceof AST_DefClass) fixed = to_class_expr(fixed);
if (fixed instanceof AST_LambdaDefinition) fixed = to_func_expr(fixed); if (fixed instanceof AST_LambdaDefinition) fixed = to_func_expr(fixed);
if (fixed instanceof AST_Lambda) { if (fixed instanceof AST_Lambda) {
var scope = self.scope.resolve(); var scope = self.scope.resolve();
@ -10719,20 +10854,21 @@ merge(Compressor.prototype, {
|| alternative.expression instanceof AST_PropAccess) || alternative.expression instanceof AST_PropAccess)
|| is_tail_equivalent(consequent.expression, alternative.expression); || is_tail_equivalent(consequent.expression, alternative.expression);
} }
if (consequent instanceof AST_Dot) return consequent.property == alternative.property; if (!(consequent instanceof AST_PropAccess)) return;
if (consequent instanceof AST_Sub) return consequent.property.equivalent_to(alternative.property); var p = consequent.property;
var q = alternative.property;
return (p instanceof AST_Node ? p.equivalent_to(q) : p == q)
&& !(consequent.expression instanceof AST_Super || alternative.expression instanceof AST_Super);
} }
function combine_tail(consequent, alternative, top) { function combine_tail(consequent, alternative, top) {
if (!is_tail_equivalent(consequent, alternative)) return !top && make_node(AST_Conditional, self, { if (!is_tail_equivalent(consequent, alternative)) return !top && make_node(AST_Conditional, self, {
condition: condition, condition: condition,
consequent: consequent, consequent: consequent,
alternative: alternative alternative: alternative,
}); });
var exp = combine_tail(consequent.expression, alternative.expression);
if (!exp) return;
var node = consequent.clone(); var node = consequent.clone();
node.expression = exp; node.expression = combine_tail(consequent.expression, alternative.expression);
return node; return node;
} }
@ -10960,6 +11096,21 @@ merge(Compressor.prototype, {
} }
}); });
AST_Arrow.DEFMETHOD("contains_super", return_false);
AST_AsyncArrow.DEFMETHOD("contains_super", return_false);
AST_Lambda.DEFMETHOD("contains_super", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {
if (result) return true;
if (node instanceof AST_Super) return result = true;
if (node !== self && node instanceof AST_Scope && !is_arrow(node)) return true;
}));
return result;
});
AST_LambdaDefinition.DEFMETHOD("contains_super", return_false);
AST_Scope.DEFMETHOD("contains_super", return_false);
AST_Arrow.DEFMETHOD("contains_this", return_false); AST_Arrow.DEFMETHOD("contains_this", return_false);
AST_AsyncArrow.DEFMETHOD("contains_this", return_false); AST_AsyncArrow.DEFMETHOD("contains_this", return_false);
AST_Scope.DEFMETHOD("contains_this", function() { AST_Scope.DEFMETHOD("contains_this", function() {
@ -10973,6 +11124,12 @@ merge(Compressor.prototype, {
return result; return result;
}); });
function can_hoist_property(prop) {
return prop instanceof AST_ObjectKeyVal
&& typeof prop.key == "string"
&& !(prop instanceof AST_ObjectMethod && prop.value.contains_super());
}
AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) { AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
if (!compressor.option("properties")) return; if (!compressor.option("properties")) return;
var expr = this.expression; var expr = this.expression;
@ -10981,9 +11138,7 @@ merge(Compressor.prototype, {
for (var i = props.length; --i >= 0;) { for (var i = props.length; --i >= 0;) {
var prop = props[i]; var prop = props[i];
if (prop.key != key) continue; if (prop.key != key) continue;
if (!all(props, function(prop) { if (!all(props, can_hoist_property)) break;
return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string";
})) break;
if (!safe_to_flatten(prop.value, compressor)) break; if (!safe_to_flatten(prop.value, compressor)) break;
return make_node(AST_Sub, this, { return make_node(AST_Sub, this, {
expression: make_node(AST_Array, expr, { expression: make_node(AST_Array, expr, {
@ -11145,7 +11300,7 @@ merge(Compressor.prototype, {
key = prop.key = "" + key; key = prop.key = "" + key;
} }
} }
if (prop instanceof AST_ObjectKeyVal && typeof key == "string") { if (can_hoist_property(prop)) {
if (prop.value.has_side_effects(compressor)) flush(); if (prop.value.has_side_effects(compressor)) flush();
keys.add(key, prop); keys.add(key, prop);
} else { } else {

View File

@ -671,6 +671,7 @@ function OutputStream(options) {
} }
PARENS(AST_AsyncFunction, needs_parens_function); PARENS(AST_AsyncFunction, needs_parens_function);
PARENS(AST_AsyncGeneratorFunction, needs_parens_function); PARENS(AST_AsyncGeneratorFunction, needs_parens_function);
PARENS(AST_ClassExpression, needs_parens_function);
PARENS(AST_Function, needs_parens_function); PARENS(AST_Function, needs_parens_function);
PARENS(AST_GeneratorFunction, needs_parens_function); PARENS(AST_GeneratorFunction, needs_parens_function);
@ -688,6 +689,9 @@ function OutputStream(options) {
// (await x)(y) // (await x)(y)
// new (await x) // new (await x)
if (p instanceof AST_Call) return p.expression === this; if (p instanceof AST_Call) return p.expression === this;
// class extends (x++) {}
// class x extends (typeof y) {}
if (p instanceof AST_Class) return true;
// (x++)[y] // (x++)[y]
// (typeof x).y // (typeof x).y
if (p instanceof AST_PropAccess) return p.expression === this; if (p instanceof AST_PropAccess) return p.expression === this;
@ -707,6 +711,12 @@ function OutputStream(options) {
|| p instanceof AST_Binary || p instanceof AST_Binary
// new (foo, bar) or foo(1, (2, 3), 4) // new (foo, bar) or foo(1, (2, 3), 4)
|| p instanceof AST_Call || p instanceof AST_Call
// class extends (foo, bar) {}
// class foo extends (bar, baz) {}
|| p instanceof AST_Class
// class { foo = (bar, baz) }
// class { [(foo, bar)]() {} }
|| p instanceof AST_ClassProperty
// (false, true) ? (a = 10, b = 20) : (c = 30) // (false, true) ? (a = 10, b = 20) : (c = 30)
// ---> 20 (side effect, set a := 10 and b := 20) // ---> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional || p instanceof AST_Conditional
@ -747,6 +757,9 @@ function OutputStream(options) {
} }
// (foo && bar)() // (foo && bar)()
if (p instanceof AST_Call) return p.expression === this; if (p instanceof AST_Call) return p.expression === this;
// class extends (foo && bar) {}
// class foo extends (bar || null) {}
if (p instanceof AST_Class) return true;
// (foo && bar)["prop"], (foo && bar).prop // (foo && bar)["prop"], (foo && bar).prop
if (p instanceof AST_PropAccess) return p.expression === this; if (p instanceof AST_PropAccess) return p.expression === this;
// typeof (foo && bar) // typeof (foo && bar)
@ -810,6 +823,9 @@ function OutputStream(options) {
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 === self; if (p instanceof AST_Call) return p.expression === self;
// class extends (a = foo) {}
// class foo extends (bar ? baz : moo) {}
if (p instanceof AST_Class) return true;
// (a = foo) ? bar : baz // (a = foo) ? bar : baz
if (p instanceof AST_Conditional) return p.condition === self; if (p instanceof AST_Conditional) return p.condition === self;
// (a = foo)["prop"] —or— (a = foo).prop // (a = foo)["prop"] —or— (a = foo).prop
@ -1015,7 +1031,9 @@ function OutputStream(options) {
output.print("default"); output.print("default");
output.space(); output.space();
this.body.print(output); this.body.print(output);
if (!(this.body instanceof AST_Lambda) || is_arrow(this.body)) output.semicolon(); if (this.body instanceof AST_Class) return;
if (this.body instanceof AST_Lambda && !is_arrow(this.body)) return;
output.semicolon();
}); });
DEFPRINT(AST_ExportForeign, function(output) { DEFPRINT(AST_ExportForeign, function(output) {
var self = this; var self = this;
@ -1157,6 +1175,59 @@ function OutputStream(options) {
DEFPRINT(AST_GeneratorDefun, print_generator); DEFPRINT(AST_GeneratorDefun, print_generator);
DEFPRINT(AST_GeneratorFunction, print_generator); DEFPRINT(AST_GeneratorFunction, print_generator);
/* -----[ classes ]----- */
DEFPRINT(AST_Class, function(output) {
var self = this;
output.print("class");
if (self.name) {
output.space();
self.name.print(output);
}
if (self.extends) {
output.space();
output.print("extends");
output.space();
self.extends.print(output);
}
output.space();
print_properties(self, output, true);
});
DEFPRINT(AST_ClassField, function(output) {
var self = this;
if (self.static) {
output.print("static");
output.space();
}
print_property_key(self, output);
if (self.value) {
output.space();
output.print("=");
output.space();
self.value.print(output);
}
output.semicolon();
});
DEFPRINT(AST_ClassGetter, print_accessor("get"));
DEFPRINT(AST_ClassSetter, print_accessor("set"));
function print_method(self, output) {
var fn = self.value;
if (is_async(fn)) {
output.print("async");
output.space();
}
if (is_generator(fn)) output.print("*");
print_property_key(self, output);
print_lambda(self.value, output);
}
DEFPRINT(AST_ClassMethod, function(output) {
var self = this;
if (self.static) {
output.print("static");
output.space();
}
print_method(self, output);
});
/* -----[ jumps ]----- */ /* -----[ jumps ]----- */
function print_jump(kind, prop) { function print_jump(kind, prop) {
return function(output) { return function(output) {
@ -1526,12 +1597,12 @@ function OutputStream(options) {
}); });
else print_braced_empty(this, output); else print_braced_empty(this, output);
}); });
function print_properties(self, output) { function print_properties(self, output, no_comma) {
var props = self.properties; var props = self.properties;
if (props.length > 0) output.with_block(function() { if (props.length > 0) output.with_block(function() {
props.forEach(function(prop, i) { props.forEach(function(prop, i) {
if (i) { if (i) {
output.print(","); if (!no_comma) output.print(",");
output.newline(); output.newline();
} }
output.indent(); output.indent();
@ -1557,7 +1628,9 @@ function OutputStream(options) {
output.print(make_num(key)); output.print(make_num(key));
} else { } else {
var quote = self.start && self.start.quote; var quote = self.start && self.start.quote;
if (RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) { if (self.private) {
output.print_name(key);
} else if (RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) {
if (quote && output.option("keep_quoted_props")) { if (quote && output.option("keep_quoted_props")) {
output.print_string(key, quote); output.print_string(key, quote);
} else { } else {
@ -1576,9 +1649,16 @@ function OutputStream(options) {
self.value.print(output); self.value.print(output);
} }
DEFPRINT(AST_ObjectKeyVal, print_key_value); DEFPRINT(AST_ObjectKeyVal, print_key_value);
DEFPRINT(AST_ObjectMethod, function(output) {
print_method(this, output);
});
function print_accessor(type) { function print_accessor(type) {
return function(output) { return function(output) {
var self = this; var self = this;
if (self.static) {
output.print("static");
output.space();
}
output.print(type); output.print(type);
output.space(); output.space();
print_property_key(self, output); print_property_key(self, output);
@ -1615,9 +1695,6 @@ function OutputStream(options) {
print_symbol(self, output); print_symbol(self, output);
}); });
DEFPRINT(AST_Hole, noop); DEFPRINT(AST_Hole, noop);
DEFPRINT(AST_This, function(output) {
output.print("this");
});
DEFPRINT(AST_Template, function(output) { DEFPRINT(AST_Template, function(output) {
var self = this; var self = this;
if (self.tag) self.tag.print(output); if (self.tag) self.tag.print(output);

View File

@ -44,10 +44,10 @@
"use strict"; "use strict";
var KEYWORDS = "break case catch const continue debugger default delete do else finally for function if in instanceof let new return switch throw try typeof var void while with"; var KEYWORDS = "break case catch class const continue debugger default delete do else extends finally for function if in instanceof let new return switch throw try typeof var void while with";
var KEYWORDS_ATOM = "false null true"; var KEYWORDS_ATOM = "false null true";
var RESERVED_WORDS = [ var RESERVED_WORDS = [
"abstract async await boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield", "abstract async await boolean byte char double enum export final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield",
KEYWORDS_ATOM, KEYWORDS_ATOM,
KEYWORDS, KEYWORDS,
].join(" "); ].join(" ");
@ -886,6 +886,10 @@ function parse($TEXT, options) {
next(); next();
return break_cont(AST_Break); return break_cont(AST_Break);
case "class":
next();
return class_(AST_DefClass);
case "const": case "const":
next(); next();
var node = const_(); var node = const_();
@ -1047,6 +1051,128 @@ function parse($TEXT, options) {
return stat; return stat;
} }
function class_(ctor) {
var was_async = S.in_async;
var was_gen = S.in_generator;
S.input.push_directives_stack();
S.input.add_directive("use strict");
var name;
if (ctor === AST_DefClass) {
name = as_symbol(AST_SymbolDefClass);
} else {
name = as_symbol(AST_SymbolClass, true);
}
var parent = null;
if (is("keyword", "extends")) {
next();
handle_regexp();
parent = expr_atom(true);
}
expect("{");
var props = [];
while (!is("punc", "}")) {
if (is("punc", ";")) {
next();
continue;
}
var start = S.token;
var fixed = is("name", "static");
if (fixed) next();
var async = is("name", "async") && peek();
if (async) {
if (async.type == "punc" && /^[(=;}]$/.test(async.value) || has_newline_before(async)) {
async = false;
} else {
async = next();
}
}
if (is("operator", "*")) {
next();
var internal = is("name") && /^#/.test(S.token.value);
var key = as_property_key();
var gen_start = S.token;
var gen = function_(async ? AST_AsyncGeneratorFunction : AST_GeneratorFunction);
gen.start = gen_start;
gen.end = prev();
props.push(new AST_ClassMethod({
start: start,
static: fixed,
private: internal,
key: key,
value: gen,
end: prev(),
}));
continue;
}
var internal = is("name") && /^#/.test(S.token.value);
var key = as_property_key();
if (is("punc", "(")) {
var func_start = S.token;
var func = function_(async ? AST_AsyncFunction : AST_Function);
func.start = func_start;
func.end = prev();
props.push(new AST_ClassMethod({
start: start,
static: fixed,
private: internal,
key: key,
value: func,
end: prev(),
}));
continue;
}
if (async) unexpected(async);
var value = null;
if (is("operator", "=")) {
next();
S.in_async = false;
S.in_generator = false;
value = maybe_assign();
S.in_generator = was_gen;
S.in_async = was_async;
} else if (!(is("punc", ";") || is("punc", "}"))) {
var type = null;
switch (key) {
case "get":
type = AST_ClassGetter;
break;
case "set":
type = AST_ClassSetter;
break;
}
if (type) {
props.push(new type({
start: start,
static: fixed,
private: is("name") && /^#/.test(S.token.value),
key: as_property_key(),
value: create_accessor(),
end: prev(),
}));
continue;
}
}
semicolon();
props.push(new AST_ClassField({
start: start,
static: fixed,
private: internal,
key: key,
value: value,
end: prev(),
}));
}
next();
S.input.pop_directives_stack();
S.in_generator = was_gen;
S.in_async = was_async;
return new ctor({
extends: parent,
name: name,
properties: props,
});
}
function for_() { function for_() {
var await = is("name", "await") && next(); var await = is("name", "await") && next();
expect("("); expect("(");
@ -1365,13 +1491,12 @@ function parse($TEXT, options) {
return new AST_ExportDeclaration({ body: export_decl() }); return new AST_ExportDeclaration({ body: export_decl() });
} }
function maybe_named(def, exp) { function maybe_named(def, expr) {
var node = function_(exp); if (expr.name) {
if (node.name) { expr = new def(expr);
node = new def(node); expr.name = new (def === AST_DefClass ? AST_SymbolDefClass : AST_SymbolDefun)(expr.name);
node.name = new AST_SymbolDefun(node.name);
} }
return node; return expr;
} }
function export_default_decl() { function export_default_decl() {
@ -1380,14 +1505,17 @@ function parse($TEXT, options) {
if (!is_token(peek(), "keyword", "function")) return; if (!is_token(peek(), "keyword", "function")) return;
next(); next();
next(); next();
if (!is("operator", "*")) return maybe_named(AST_AsyncDefun, AST_AsyncFunction); if (!is("operator", "*")) return maybe_named(AST_AsyncDefun, function_(AST_AsyncFunction));
next(); next();
return maybe_named(AST_AsyncGeneratorDefun, AST_AsyncGeneratorFunction); return maybe_named(AST_AsyncGeneratorDefun, function_(AST_AsyncGeneratorFunction));
case "class":
next();
return maybe_named(AST_DefClass, class_(AST_ClassExpression));
case "function": case "function":
next(); next();
if (!is("operator", "*")) return maybe_named(AST_Defun, AST_Function); if (!is("operator", "*")) return maybe_named(AST_Defun, function_(AST_Function));
next(); next();
return maybe_named(AST_GeneratorDefun, AST_GeneratorFunction); return maybe_named(AST_GeneratorDefun, function_(AST_GeneratorFunction));
} }
} }
@ -1399,6 +1527,9 @@ function parse($TEXT, options) {
if (!is("operator", "*")) return function_(AST_AsyncDefun); if (!is("operator", "*")) return function_(AST_AsyncDefun);
next(); next();
return function_(AST_AsyncGeneratorDefun); return function_(AST_AsyncGeneratorDefun);
case "class":
next();
return class_(AST_DefClass);
case "const": case "const":
next(); next();
var node = const_(); var node = const_();
@ -1706,7 +1837,14 @@ function parse($TEXT, options) {
} }
unexpected(); unexpected();
} }
if (is("keyword", "function")) { if (is("keyword")) switch (start.value) {
case "class":
next();
var clazz = class_(AST_ClassExpression);
clazz.start = start;
clazz.end = prev();
return subscripts(clazz, allow_calls);
case "function":
next(); next();
var func; var func;
if (is("operator", "*")) { if (is("operator", "*")) {
@ -1813,7 +1951,7 @@ function parse($TEXT, options) {
var gen = function_(AST_GeneratorFunction); var gen = function_(AST_GeneratorFunction);
gen.start = gen_start; gen.start = gen_start;
gen.end = prev(); gen.end = prev();
a.push(new AST_ObjectKeyVal({ a.push(new AST_ObjectMethod({
start: start, start: start,
key: key, key: key,
value: gen, value: gen,
@ -1862,7 +2000,7 @@ function parse($TEXT, options) {
var func = function_(AST_Function); var func = function_(AST_Function);
func.start = func_start; func.start = func_start;
func.end = prev(); func.end = prev();
a.push(new AST_ObjectKeyVal({ a.push(new AST_ObjectMethod({
start: start, start: start,
key: key, key: key,
value: func, value: func,
@ -1888,7 +2026,7 @@ function parse($TEXT, options) {
var func = function_(is_gen ? AST_AsyncGeneratorFunction : AST_AsyncFunction); var func = function_(is_gen ? AST_AsyncGeneratorFunction : AST_AsyncFunction);
func.start = func_start; func.start = func_start;
func.end = prev(); func.end = prev();
a.push(new AST_ObjectKeyVal({ a.push(new AST_ObjectMethod({
start: start, start: start,
key: key, key: key,
value: func, value: func,
@ -1948,9 +2086,21 @@ function parse($TEXT, options) {
function _make_symbol(type, token) { function _make_symbol(type, token) {
var name = token.value; var name = token.value;
if (name === "await" && S.in_async) unexpected(token); switch (name) {
if (name === "yield" && S.in_generator) unexpected(token); case "await":
return new (name === "this" ? AST_This : type)({ if (S.in_async) unexpected(token);
break;
case "super":
type = AST_Super;
break;
case "this":
type = AST_This;
break;
case "yield":
if (S.in_generator) unexpected(token);
break;
}
return new type({
name: "" + name, name: "" + name,
start: token, start: token,
end: token, end: token,

View File

@ -124,6 +124,19 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
var next_def_id = 0; var next_def_id = 0;
var scope = self.parent_scope = null; var scope = self.parent_scope = null;
var tw = new TreeWalker(function(node, descend) { var tw = new TreeWalker(function(node, descend) {
if (node instanceof AST_DefClass) {
var save_exported = exported;
exported = tw.parent() instanceof AST_ExportDeclaration;
node.name.walk(tw);
exported = save_exported;
walk_scope(function() {
if (node.extends) node.extends.walk(tw);
node.properties.forEach(function(prop) {
prop.walk(tw);
});
});
return true;
}
if (node instanceof AST_Definitions) { if (node instanceof AST_Definitions) {
var save_exported = exported; var save_exported = exported;
exported = tw.parent() instanceof AST_ExportDeclaration; exported = tw.parent() instanceof AST_ExportDeclaration;

View File

@ -147,6 +147,15 @@ TreeTransformer.prototype = new TreeWalker;
} }
DEF(AST_Arrow, transform_arrow); DEF(AST_Arrow, transform_arrow);
DEF(AST_AsyncArrow, transform_arrow); DEF(AST_AsyncArrow, transform_arrow);
DEF(AST_Class, function(self, tw) {
if (self.name) self.name = self.name.transform(tw);
if (self.extends) self.extends = self.extends.transform(tw);
self.properties = do_list(self.properties, tw);
});
DEF(AST_ClassProperty, function(self, tw) {
if (self.key instanceof AST_Node) self.key = self.key.transform(tw);
if (self.value) self.value = self.value.transform(tw);
});
DEF(AST_Call, function(self, tw) { DEF(AST_Call, function(self, tw) {
self.expression = self.expression.transform(tw); self.expression = self.expression.transform(tw);
self.args = do_list(self.args, tw); self.args = do_list(self.args, tw);

574
test/compress/classes.js Normal file
View File

@ -0,0 +1,574 @@
constructor_1: {
input: {
"use strict";
console.log(new class {
constructor(a) {
this.a = a;
}
}("PASS").a);
}
expect_exact: '"use strict";console.log(new class{constructor(a){this.a=a}}("PASS").a);'
expect_stdout: "PASS"
node_version: ">=4"
}
constructor_2: {
input: {
"use strict";
console.log(new class {
"constructor"(a) {
this.a = a;
}
}("PASS").a);
}
expect_exact: '"use strict";console.log(new class{constructor(a){this.a=a}}("PASS").a);'
expect_stdout: "PASS"
node_version: ">=4"
}
constructor_3: {
input: {
"use strict";
console.log(new class {
["constructor"](a) {
this.a = a;
}
}("FAIL").a || "PASS");
}
expect_exact: '"use strict";console.log(new class{["constructor"](a){this.a=a}}("FAIL").a||"PASS");'
expect_stdout: "PASS"
node_version: ">=4"
}
constructor_4: {
input: {
"use strict";
class A {
static constructor(a) {
console.log(a);
}
}
A.constructor("PASS");
}
expect_exact: '"use strict";class A{static constructor(a){console.log(a)}}A.constructor("PASS");'
expect_stdout: "PASS"
node_version: ">=4"
}
fields: {
input: {
var o = new class A {
"#p";
static #p = "PASS";
async
get
q() {
return A.#p;
}
;
[6 * 7] = console ? "foo" : "bar"
};
for (var k in o)
console.log(k, o[k]);
console.log(o.q);
}
expect_exact: 'var o=new class A{"#p";static #p="PASS";async;get q(){return A.#p}[6*7]=console?"foo":"bar"};for(var k in o)console.log(k,o[k]);console.log(o.q);'
expect_stdout: [
"42 foo",
"#p undefined",
"async undefined",
"PASS",
]
node_version: ">=12"
}
methods: {
input: {
"use strict";
class A {
static f() {
return "foo";
}
*g() {
yield A.f();
yield "bar";
}
}
for (var a of new A().g())
console.log(a);
}
expect_exact: '"use strict";class A{static f(){return"foo"}*g(){yield A.f();yield"bar"}}for(var a of(new A).g())console.log(a);'
expect_stdout: [
"foo",
"bar",
]
node_version: ">=4"
}
private_methods: {
input: {
new class A {
static *#f() {
yield A.#p * 3;
}
async #g() {
for (var a of A.#f())
return a * await 2;
}
static get #p() {
return 7;
}
get q() {
return this.#g();
}
}().q.then(console.log);
}
expect_exact: "(new class A{static*#f(){yield 3*A.#p}async #g(){for(var a of A.#f())return a*await 2}static get #p(){return 7}get q(){return this.#g()}}).q.then(console.log);"
expect_stdout: "42"
node_version: ">=14"
}
await: {
input: {
var await = "PASS";
(async function() {
return await new class extends (await function() {}) { [await 42] = await };
})().then(function(o) {
console.log(o[42]);
});
}
expect_exact: 'var await="PASS";(async function(){return await new class extends(await function(){}){[await 42]=await}})().then(function(o){console.log(o[42])});'
expect_stdout: "PASS"
node_version: ">=12"
}
yield: {
input: {
var a = function*() {
yield new class { [yield "foo"] = "bar" };
}();
console.log(a.next().value);
console.log(a.next(42).value[42]);
}
expect_exact: 'var a=function*(){yield new class{[yield"foo"]="bar"}}();console.log(a.next().value);console.log(a.next(42).value[42]);'
expect_stdout: [
"foo",
"bar",
]
node_version: ">=12"
}
conditional_parenthesis: {
options = {
conditionals: true,
}
input: {
"use strict";
if (class {})
console.log("PASS");
}
expect_exact: '"use strict";(class{})&&console.log("PASS");'
expect_stdout: "PASS"
node_version: ">=4"
}
class_super: {
input: {
"use strict";
class A {
static get p() {
return "Foo";
}
static get q() {
return super.p || 42;
}
constructor() {
console.log("a.p", super.p, this.p);
console.log("a.q", super.q, this.q);
}
get p() {
return "foo";
}
get q() {
return super.p || null;
}
}
class B extends A {
static get p() {
return "Bar";
}
static get q() {
return super.p;
}
constructor() {
super();
console.log("b.p", super.p, this.p);
console.log("b.q", super.q, this.q);
}
get p() {
return "bar";
}
get q() {
return super.p;
}
}
console.log("A", A.p, A.q);
console.log("B", B.p, B.q);
new B();
}
expect_exact: '"use strict";class A{static get p(){return"Foo"}static get q(){return super.p||42}constructor(){console.log("a.p",super.p,this.p);console.log("a.q",super.q,this.q)}get p(){return"foo"}get q(){return super.p||null}}class B extends A{static get p(){return"Bar"}static get q(){return super.p}constructor(){super();console.log("b.p",super.p,this.p);console.log("b.q",super.q,this.q)}get p(){return"bar"}get q(){return super.p}}console.log("A",A.p,A.q);console.log("B",B.p,B.q);new B;'
expect_stdout: [
"A Foo 42",
"B Bar Foo",
"a.p undefined bar",
"a.q undefined foo",
"b.p foo bar",
"b.q null foo",
]
node_version: ">=4"
}
block_scoped: {
options = {
evaluate: true,
dead_code: true,
loops: true,
}
input: {
"use strict";
while (0) {
class A {}
}
if (console) {
class B {}
}
console.log(typeof A, typeof B);
}
expect: {
"use strict";
0;
if (console) {
class B {}
}
console.log(typeof A, typeof B);
}
expect_stdout: "undefined undefined"
node_version: ">=4"
}
keep_extends: {
options = {
toplevel: true,
unused: true,
}
input: {
"use strict";
try {
class A extends 42 {}
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
(class extends 42 {});
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
drop_name: {
options = {
unused: true,
}
input: {
"use strict";
try {
console.log(class A extends 42 {})
} catch (e) {
console.log("PASS");
}
}
expect: {
"use strict";
try {
console.log(class extends 42 {})
} catch (e) {
console.log("PASS");
}
}
expect_stdout: "PASS"
node_version: ">=4"
}
separate_name: {
options = {
merge_vars: true,
toplevel: true,
}
input: {
"use strict";
class A {
constructor(v) {
this.p = v;
}
}
var a = new A("PASS");
console.log(a.p);
}
expect: {
"use strict";
class A {
constructor(v) {
this.p = v;
}
}
var a = new A("PASS");
console.log(a.p);
}
expect_stdout: "PASS"
node_version: ">=4"
}
static_side_effects: {
options = {
inline: true,
toplevel: true,
unused: true,
}
input: {
var a = "FAIL 1";
class A {
static p = a = "PASS";
q = a = "FAIL 2";
}
console.log(a);
}
expect: {
var a = "FAIL 1";
a = "PASS";
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=12"
}
single_use: {
options = {
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
class A {}
console.log(typeof new A());
}
expect: {
"use strict";
console.log(typeof new class {}());
}
expect_stdout: "object"
node_version: ">=4"
}
collapse_non_strict: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
var a = 42..p++;
new class extends (a || function() {
console.log("PASS");
}) {}
}
expect: {
var a = 42..p++;
new class extends (a || function() {
console.log("PASS");
}) {}
}
expect_stdout: "PASS"
node_version: ">=6"
}
collapse_rhs: {
options = {
collapse_vars: true,
}
input: {
"use strict";
var a = "FAIL";
a = "PASS";
class A {
p = "PASS";
}
console.log(a);
}
expect: {
"use strict";
var a = "FAIL";
a = "PASS";
class A {
p = "PASS";
}
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=12"
}
collapse_rhs_static: {
options = {
collapse_vars: true,
}
input: {
"use strict";
var a = "FAIL";
a = "PASS";
class A {
static p = "PASS";
}
console.log(a);
}
expect: {
"use strict";
var a = "FAIL";
class A {
static p = a = "PASS";
}
console.log(a);
}
expect_stdout: "PASS"
node_version: ">=12"
}
property_side_effects: {
options = {
inline: true,
keep_fargs: false,
unused: true,
}
input: {
"use strict";
(function f(a, b) {
class A {
[a.log("PASS")]() {
b.log("FAIL");
}
}
})(console, console);
}
expect: {
"use strict";
(function(a) {
a.log("PASS");
})(console, console);
}
expect_stdout: "PASS"
node_version: ">=4"
}
property_side_effects_static: {
options = {
inline: true,
keep_fargs: false,
unused: true,
}
input: {
"use strict";
(function f(a, b) {
class A {
static [a.log("PASS")]() {
b.log("FAIL");
}
}
})(console, console);
}
expect: {
"use strict";
(function(a) {
a.log("PASS");
})(console, console);
}
expect_stdout: "PASS"
node_version: ">=4"
}
unused_await: {
options = {
inline: true,
unused: true,
}
input: {
var await = "PASS";
(async function() {
class A {
static p = console.log(await);
}
})();
}
expect: {
var await = "PASS";
(async function() {
(() => console.log(await))();
})();
}
expect_stdout: "PASS"
node_version: ">=12"
}
computed_key_side_effects: {
options = {
evaluate: true,
reduce_vars: true,
side_effects: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
var a = 0;
class A {
[(a++, 0)]() {}
}
console.log(a);
}
expect: {
"use strict";
console.log(1);
}
expect_stdout: "1"
node_version: ">=4"
}
computed_key_generator: {
options = {
unused: true,
}
input: {
"use strict";
var a = function*() {
class A {
static [console.log(yield)]() {}
}
}();
a.next("FAIL");
a.next("PASS");
}
expect: {
"use strict";
var a = function*() {
console.log(yield);
}();
a.next("FAIL");
a.next("PASS");
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@ -1861,3 +1861,39 @@ issue_3808_2: {
} }
expect_stdout: " PASS" expect_stdout: " PASS"
} }
object_super: {
options = {
conditionals: true,
}
input: {
Object.setPrototypeOf({
f(a) {
a ? this.g("FAIL") : super.g("FAIL");
},
g(b) {
console.log(b);
},
}, {
g() {
console.log("PASS");
},
}).f();
}
expect: {
Object.setPrototypeOf({
f(a) {
a ? this.g("FAIL") : super.g("FAIL");
},
g(b) {
console.log(b);
},
}, {
g() {
console.log("PASS");
},
}).f();
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@ -17,22 +17,25 @@ var_defs: {
defuns: { defuns: {
input: { input: {
export class A {}
export function e() {} export function e() {}
export function* f(a) {} export function* f(a) {}
export async function g(b, c) {} export async function g(b, c) {}
export async function* h({}, ...[]) {} export async function* h({}, ...[]) {}
} }
expect_exact: "export function e(){}export function*f(a){}export async function g(b,c){}export async function*h({},...[]){}" expect_exact: "export class A{}export function e(){}export function*f(a){}export async function g(b,c){}export async function*h({},...[]){}"
} }
defaults: { defaults: {
input: { input: {
export default 42; export default 42;
export default async;
export default (x, y) => x * x; export default (x, y) => x * x;
export default class {};
export default function*(a, b) {}; export default function*(a, b) {};
export default async function f({ c }, ...[ d ]) {}; export default async function f({ c }, ...[ d ]) {};
} }
expect_exact: "export default 42;export default(x,y)=>x*x;export default function*(a,b){}export default async function f({c:c},...[d]){}" expect_exact: "export default 42;export default async;export default(x,y)=>x*x;export default class{}export default function*(a,b){}export default async function f({c:c},...[d]){}"
} }
defaults_parenthesis_1: { defaults_parenthesis_1: {
@ -88,18 +91,22 @@ drop_unused: {
input: { input: {
export default 42; export default 42;
export default (x, y) => x * x; export default (x, y) => x * x;
export default function*(a, b) {}; export default class A extends B { get p() { h() } }
export default async function f({ c }, ...[ d ]) {}; export default function*(a, b) {}
export default async function f({ c }, ...[ d ]) {}
export var e; export var e;
export function g(x, [ y ], ...z) {} export function g(x, [ y ], ...z) {}
function h() {}
} }
expect: { expect: {
export default 42; export default 42;
export default (x, y) => x * x; export default (x, y) => x * x;
export default function*(a, b) {}; export default class extends B { get p() { h() } }
export default async function({}) {}; export default function*(a, b) {}
export default async function({}) {}
export var e; export var e;
export function g(x, []) {} export function g(x, []) {}
function h() {}
} }
} }
@ -195,3 +202,28 @@ hoist_exports: {
export { f as bbb, o as ccc, c as fff }; export { f as bbb, o as ccc, c as fff };
} }
} }
keep_return_values: {
options = {
booleans: true,
evaluate: true,
reduce_vars: true,
toplevel: true,
}
input: {
export default function() {
return [];
}
export default function f() {
return null;
}
}
expect: {
export default function() {
return [];
}
export default function f() {
return null;
}
}
}

View File

@ -1068,3 +1068,29 @@ issue_4023: {
} }
expect_stdout: "true" expect_stdout: "true"
} }
object_super: {
options = {
hoist_props: true,
reduce_vars: true,
toplevel: true,
}
input: {
var o = {
f(a) {
return a ? console.log("PASS") : super.log("PASS");
},
};
o.f(42);
}
expect: {
var o = {
f(a) {
return a ? console.log("PASS") : super.log("PASS");
},
};
o.f(42);
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@ -144,3 +144,24 @@ keep_ref: {
foo(); foo();
} }
} }
forbid_merge: {
options = {
merge_vars: true,
toplevel: true,
}
input: {
import A from "foo";
export default class extends A {}
var f = () => () => {};
f();
f();
}
expect: {
import A from "foo";
export default class extends A {}
var f = () => () => {};
f();
f();
}
}

View File

@ -280,6 +280,72 @@ shorthand_keywords: {
node_version: ">=6" node_version: ">=6"
} }
object_super: {
input: {
var o = {
f() {
return super.p;
},
p: "FAIL",
};
Object.setPrototypeOf(o, { p: "PASS" });
console.log(o.f());
}
expect_exact: 'var o={f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});console.log(o.f());'
expect_stdout: "PASS"
node_version: ">=4"
}
object_super_async: {
input: {
var o = {
async f() {
return super.p;
},
p: "FAIL",
};
Object.setPrototypeOf(o, { p: "PASS" });
o.f().then(console.log);
}
expect_exact: 'var o={async f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});o.f().then(console.log);'
expect_stdout: "PASS"
node_version: ">=8"
}
object_super_generator: {
input: {
var o = {
*f() {
yield super.p;
},
p: "FAIL",
};
Object.setPrototypeOf(o, { p: "PASS" });
console.log(o.f().next().value);
}
expect_exact: 'var o={*f(){yield super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});console.log(o.f().next().value);'
expect_stdout: "PASS"
node_version: ">=4"
}
object_super_async_generator: {
input: {
var o = {
async *f() {
return super.p;
},
p: "FAIL",
};
Object.setPrototypeOf(o, { p: "PASS" });
o.f().next().then(function(v) {
console.log(v.value, v.done);
});
}
expect_exact: 'var o={async*f(){return super.p},p:"FAIL"};Object.setPrototypeOf(o,{p:"PASS"});o.f().next().then(function(v){console.log(v.value,v.done)});'
expect_stdout: "PASS true"
node_version: ">=10"
}
issue_4269_1: { issue_4269_1: {
options = { options = {
evaluate: true, evaluate: true,

View File

@ -1378,3 +1378,25 @@ issue_3389: {
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }
object_super: {
options = {
properties: true,
}
input: {
({
f(a) {
return a ? console.log("PASS") : super.log("PASS");
},
}).f(console);
}
expect: {
({
f(a) {
return a ? console.log("PASS") : super.log("PASS");
},
}).f(console);
}
expect_stdout: "PASS"
node_version: ">=4"
}

View File

@ -1,5 +1,6 @@
exports["Compressor"] = Compressor; exports["Compressor"] = Compressor;
exports["defaults"] = defaults; exports["defaults"] = defaults;
exports["is_statement"] = is_statement;
exports["JS_Parse_Error"] = JS_Parse_Error; exports["JS_Parse_Error"] = JS_Parse_Error;
exports["List"] = List; exports["List"] = List;
exports["mangle_properties"] = mangle_properties; exports["mangle_properties"] = mangle_properties;

View File

@ -181,7 +181,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
else if (node instanceof U.AST_Call) { else if (node instanceof U.AST_Call) {
var expr = [ var expr = [
node.expression, !(node.expression instanceof U.AST_Super) && node.expression,
node.args[0], node.args[0],
null, // intentional null, // intentional
][ ((node.start._permute += step) * steps | 0) % 3 ]; ][ ((node.start._permute += step) * steps | 0) % 3 ];
@ -202,7 +202,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
var seq = []; var seq = [];
body.forEach(function(node) { body.forEach(function(node) {
var expr = expr instanceof U.AST_Exit ? node.value : node.body; var expr = expr instanceof U.AST_Exit ? node.value : node.body;
if (expr instanceof U.AST_Node && !is_statement(expr)) { if (expr instanceof U.AST_Node && !U.is_statement(expr)) {
// collect expressions from each statements' body // collect expressions from each statements' body
seq.push(expr); seq.push(expr);
} }
@ -358,7 +358,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
else if (node instanceof U.AST_PropAccess) { else if (node instanceof U.AST_PropAccess) {
var expr = [ var expr = [
node.expression, !(node.expression instanceof U.AST_Super) && node.expression,
node.property instanceof U.AST_Node && !(parent instanceof U.AST_Destructured) && node.property, node.property instanceof U.AST_Node && !(parent instanceof U.AST_Destructured) && node.property,
][ node.start._permute++ % 2 ]; ][ node.start._permute++ % 2 ];
if (expr) { if (expr) {
@ -468,7 +468,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options)
} }
// replace this node // replace this node
var newNode = is_statement(node) ? new U.AST_EmptyStatement({ var newNode = U.is_statement(node) ? new U.AST_EmptyStatement({
start: {}, start: {},
}) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], { }) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], {
expression: true, expression: true,
@ -666,10 +666,6 @@ function is_timed_out(result) {
return sandbox.is_error(result) && /timed out/.test(result.message); return sandbox.is_error(result) && /timed out/.test(result.message);
} }
function is_statement(node) {
return node instanceof U.AST_Statement && !(node instanceof U.AST_LambdaExpression);
}
function merge_sequence(array, node) { function merge_sequence(array, node) {
if (node instanceof U.AST_Sequence) { if (node instanceof U.AST_Sequence) {
array.push.apply(array, node.expressions); array.push.apply(array, node.expressions);
@ -689,7 +685,7 @@ function to_sequence(expressions) {
} }
function to_statement(node) { function to_statement(node) {
return is_statement(node) ? node : new U.AST_SimpleStatement({ return U.is_statement(node) ? node : new U.AST_SimpleStatement({
body: node, body: node,
start: {}, start: {},
}); });

View File

@ -25,7 +25,13 @@ exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, top
} while (prev !== stdout); } while (prev !== stdout);
return stdout; return stdout;
} : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) { } : semver.satisfies(process.version, "<0.12") ? run_code_vm : function(code, toplevel, timeout) {
if (/\basync([ \t]+[^\s()[\]{},.&|!~=*%/+-]+|[ \t]*\([\s\S]*?\))[ \t]*=>|\b(async[ \t]+function|setInterval|setTimeout)\b/.test(code)) { if ([
/\basync[ \t]*\([\s\S]*?\)[ \t]*=>/,
/\b(async[ \t]+function|setInterval|setTimeout)\b/,
/\basync([ \t]+|[ \t]*\*[ \t]*)[^\s()[\]{},.&|!~=*%/+-]+(\s*\(|[ \t]*=>)/,
].some(function(pattern) {
return pattern.test(code);
})) {
return run_code_exec(code, toplevel, timeout); return run_code_exec(code, toplevel, timeout);
} else { } else {
return run_code_vm(code, toplevel, timeout); return run_code_vm(code, toplevel, timeout);

View File

@ -137,6 +137,9 @@ var SUPPORT = function(matrix) {
async_generator: "async function* f(){}", async_generator: "async function* f(){}",
bigint: "42n", bigint: "42n",
catch_omit_var: "try {} catch {}", catch_omit_var: "try {} catch {}",
class: "class C { f() {} }",
class_field: "class C { p = 0; }",
class_private: "class C { #f() {} }",
computed_key: "({[0]: 0});", computed_key: "({[0]: 0});",
const_block: "var a; { const a = 0; }", const_block: "var a; { const a = 0; }",
default_value: "[ a = 0 ] = [];", default_value: "[ a = 0 ] = [];",
@ -312,6 +315,8 @@ var DEFUN_OK = true;
var DONT_STORE = true; var DONT_STORE = true;
var NO_CONST = true; var NO_CONST = true;
var NO_DUPLICATE = true; var NO_DUPLICATE = true;
var NO_LAMBDA = true;
var NO_TEMPLATE = true;
var VAR_NAMES = [ var VAR_NAMES = [
"a", "a",
@ -352,11 +357,15 @@ var TYPEOF_OUTCOMES = [
var avoid_vars = []; var avoid_vars = [];
var block_vars = []; var block_vars = [];
var lambda_vars = [];
var unique_vars = []; var unique_vars = [];
var classes = [];
var async = false; var async = false;
var generator = false; var generator = false;
var loops = 0; var loops = 0;
var funcs = 0; var funcs = 0;
var clazz = 0;
var in_class = 0;
var called = Object.create(null); var called = Object.create(null);
var labels = 10000; var labels = 10000;
@ -372,11 +381,15 @@ function strictMode() {
function createTopLevelCode() { function createTopLevelCode() {
VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list
block_vars.length = 0; block_vars.length = 0;
lambda_vars.length = 0;
unique_vars.length = 0; unique_vars.length = 0;
classes.length = 0;
async = false; async = false;
generator = false; generator = false;
loops = 0; loops = 0;
funcs = 0; funcs = 0;
clazz = 0;
in_class = 0;
called = Object.create(null); called = Object.create(null);
return [ return [
strictMode(), strictMode(),
@ -411,7 +424,7 @@ function createParams(was_async, was_generator, noDuplicate) {
var params = []; var params = [];
for (var n = rng(4); --n >= 0;) { for (var n = rng(4); --n >= 0;) {
var name = createVarName(MANDATORY); var name = createVarName(MANDATORY);
if (noDuplicate) unique_vars.push(name); if (noDuplicate || in_class) unique_vars.push(name);
params.push(name); params.push(name);
} }
unique_vars.length = len; unique_vars.length = len;
@ -428,7 +441,7 @@ function createArgs(recurmax, stmtDepth, canThrow, noTemplate) {
case 0: case 0:
case 1: case 1:
var name = getVarName(); var name = getVarName();
if (canThrow && rng(8) === 0) { if (canThrow && rng(20) == 0) {
args.push("..." + name); args.push("..." + name);
} else { } else {
args.push('...("" + ' + name + ")"); args.push('...("" + ' + name + ")");
@ -457,9 +470,9 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was
var side_effects = []; var side_effects = [];
values.forEach(function(value, index) { values.forEach(function(value, index) {
value = fn(value, index); value = fn(value, index);
if (/]:|=/.test(value) ? canThrow && rng(10) == 0 : rng(5)) { if (/]:|=/.test(value) ? canThrow && rng(20) == 0 : rng(5)) {
declare_only.splice(rng(declare_only.length + 1), 0, value); declare_only.splice(rng(declare_only.length + 1), 0, value);
} else if (canThrow && rng(5) == 0) { } else if (canThrow && rng(20) == 0) {
side_effects.splice(rng(side_effects.length + 1), 0, value); side_effects.splice(rng(side_effects.length + 1), 0, value);
} else { } else {
side_effects.push(value); side_effects.push(value);
@ -654,6 +667,7 @@ function filterDirective(s) {
function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
var block_len = block_vars.length; var block_len = block_vars.length;
var class_len = classes.length;
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var consts = []; var consts = [];
var lets = []; var lets = [];
@ -671,13 +685,22 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) {
fn(function() { fn(function() {
consts.forEach(addAvoidVar); consts.forEach(addAvoidVar);
lets.forEach(addAvoidVar); lets.forEach(addAvoidVar);
if (rng(2)) { var s = [];
return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n"; if (SUPPORT.class) while (rng(100) == 0) {
} else { var name = "C" + clazz++;
return createDefinitions("let", lets) + "\n" + createDefinitions("const", consts) + "\n"; classes.push(name);
s.push(createClassLiteral(recurmax,stmtDepth, canThrow, name));
} }
if (rng(2)) {
s.push(createDefinitions("const", consts), createDefinitions("let", lets));
} else {
s.push(createDefinitions("let", lets), createDefinitions("const", consts));
}
s.push("");
return s.join("\n");
}); });
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
classes.length = class_len;
block_vars.length = block_len; block_vars.length = block_len;
function createDefinitions(type, names) { function createDefinitions(type, names) {
@ -730,6 +753,7 @@ function mayCreateBlockVariables(recurmax, stmtDepth, canThrow, fn) {
} }
function makeFunction(name) { function makeFunction(name) {
lambda_vars.push(name);
if (generator) { if (generator) {
name = "function* " + name; name = "function* " + name;
} else { } else {
@ -757,6 +781,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
var s = []; var s = [];
var name, args; var name, args;
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var lambda_len = lambda_vars.length;
var save_async = async; var save_async = async;
async = SUPPORT.async && rng(50) == 0; async = SUPPORT.async && rng(50) == 0;
var save_generator = generator; var save_generator = generator;
@ -801,6 +826,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) {
var call_next = invokeGenerator(save_generator); var call_next = invokeGenerator(save_generator);
generator = save_generator; generator = save_generator;
async = save_async; async = save_async;
lambda_vars.length = lambda_len;
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
if (!allowDefun) { if (!allowDefun) {
@ -920,7 +946,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
} else { } else {
init = "const "; init = "const ";
} }
if (!SUPPORT.destructuring || of && !(canThrow && rng(10) == 0) || rng(10)) { if (!SUPPORT.destructuring || of && !(canThrow && rng(20) == 0) || rng(10)) {
init += key; init += key;
} else if (rng(5)) { } else if (rng(5)) {
init += "[ " + key + " ]"; init += "[ " + key + " ]";
@ -932,7 +958,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
var await = SUPPORT.for_await_of && async && rng(20) == 0; var await = SUPPORT.for_await_of && async && rng(20) == 0;
if (SUPPORT.generator && rng(20) == 0) { if (SUPPORT.generator && rng(20) == 0) {
var gen = getVarName(); var gen = getVarName();
if (canThrow && rng(10) == 0) { if (canThrow && rng(20) == 0) {
s += gen + "; "; s += gen + "; ";
} else { } else {
s += gen + " && typeof " + gen + "[Symbol."; s += gen + " && typeof " + gen + "[Symbol.";
@ -941,7 +967,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn
} }
} else if (rng(5)) { } else if (rng(5)) {
s += createArrayLiteral(recurmax, stmtDepth, canThrow) + "; "; s += createArrayLiteral(recurmax, stmtDepth, canThrow) + "; ";
} else if (canThrow && rng(10) == 0) { } else if (canThrow && rng(20) == 0) {
s += createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; "; s += createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; ";
} else { } else {
s += '"" + (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "); "; s += '"" + (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "); ";
@ -1150,7 +1176,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return [ return [
"[ ", "[ ",
new Array(rng(3)).join(), new Array(rng(3)).join(),
getVarName(NO_CONST), getVarName(NO_CONST, NO_LAMBDA),
new Array(rng(3)).join(), new Array(rng(3)).join(),
" ] = ", " ] = ",
createArrayLiteral(recurmax, stmtDepth, canThrow), createArrayLiteral(recurmax, stmtDepth, canThrow),
@ -1159,13 +1185,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
return [ return [
"{ ", "{ ",
rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ", rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ",
getVarName(NO_CONST), getVarName(NO_CONST, NO_LAMBDA),
" } = ", " } = ",
createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), createExpression(recurmax, COMMA_OK, stmtDepth, canThrow),
" || {}", " || {}",
].join(""); ].join("");
default: default:
return getVarName(NO_CONST) + createAssignment() + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); return getVarName(NO_CONST, NO_LAMBDA) + 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);
@ -1174,6 +1200,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
case p++: case p++:
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var lambda_len = lambda_vars.length;
var save_async = async; var save_async = async;
async = SUPPORT.async && rng(50) == 0; async = SUPPORT.async && rng(50) == 0;
var save_generator = generator; var save_generator = generator;
@ -1185,9 +1212,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
generator = false; generator = false;
} }
} }
unique_vars.push("c"); unique_vars.push("a", "b", "c");
var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that. var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that.
unique_vars.pop(); unique_vars.length -= 3;
var s = []; var s = [];
switch (rng(5)) { switch (rng(5)) {
case 0: case 0:
@ -1218,8 +1245,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
s.push("(" + params); s.push("(" + params);
switch (rng(10)) { switch (rng(10)) {
case 0: case 0:
s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); if (!in_class) {
break; s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])");
break;
}
case 1: case 1:
s.push("(this && this." + getDotKey() + ")"); s.push("(this && this." + getDotKey() + ")");
break; break;
@ -1232,6 +1261,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
}); });
generator = save_generator; generator = save_generator;
async = save_async; async = save_async;
lambda_vars.length = lambda_len;
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow); if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow);
if (args) suffix += args; if (args) suffix += args;
@ -1275,24 +1305,25 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
var instantiate = rng(4) ? "new " : ""; var instantiate = rng(4) ? "new " : "";
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
s.push( s.push(
instantiate + "function " + name + "(" + createParams(save_async, save_generator) + "){", instantiate + makeFunction(name) + "(" + createParams(save_async, save_generator) + "){",
strictMode(), strictMode(),
defns() defns()
); );
if (instantiate) for (var i = rng(4); --i >= 0;) { if (instantiate) for (var i = rng(4); --i >= 0;) {
if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); s.push((in_class ? "if (this) " : "") + createThisAssignment(recurmax, stmtDepth, canThrow));
else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";");
} }
s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth));
}); });
generator = save_generator; generator = save_generator;
async = save_async; async = save_async;
lambda_vars.length = lambda_len;
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
s.push(rng(2) ? "}" : "}" + createArgs(recurmax, stmtDepth, canThrow, instantiate)); s.push(rng(2) ? "}" : "}" + createArgs(recurmax, stmtDepth, canThrow, instantiate));
break; break;
} }
generator = save_generator; generator = save_generator;
async = save_async; async = save_async;
lambda_vars.length = lambda_len;
VAR_NAMES.length = nameLenBefore; VAR_NAMES.length = nameLenBefore;
return filterDirective(s).join("\n"); return filterDirective(s).join("\n");
case p++: case p++:
@ -1358,18 +1389,40 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) {
case p++: case p++:
var name = getVarName(); var name = getVarName();
var s = name + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]"; var s = name + "[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]";
return canThrow && rng(8) == 0 ? s : name + " && " + s; return canThrow && rng(20) == 0 ? s : name + " && " + s;
case p++: case p++:
var name = getVarName(); var name = getVarName();
var s = name + "." + getDotKey(); var s = name + "." + getDotKey();
return canThrow && rng(8) == 0 ? s : name + " && " + s; return canThrow && rng(20) == 0 ? s : name + " && " + s;
case p++: case p++:
case p++: case p++:
var name = getVarName(); var name = getVarName();
var s = name + "." + getDotKey(); var s = name + "." + getDotKey();
s = "typeof " + s + ' == "function" && --_calls_ >= 0 && ' + s + createArgs(recurmax, stmtDepth, canThrow); s = "typeof " + s + ' == "function" && --_calls_ >= 0 && ' + s + createArgs(recurmax, stmtDepth, canThrow);
return canThrow && rng(8) == 0 ? s : name + " && " + s; return canThrow && rng(20) == 0 ? s : name + " && " + s;
case p++: case p++:
if (SUPPORT.class && classes.length) switch (rng(20)) {
case 0:
return "--_calls_ >= 0 && new " + classes[rng(classes.length)] + createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE);
case 1:
var s = "--_calls_ >= 0 && new ";
var nameLenBefore = VAR_NAMES.length;
var class_len = classes.length;
var name;
if (canThrow && rng(20) == 0) {
in_class++;
name = createVarName(MAYBE);
in_class--;
} else if (rng(2)) {
name = "C" + clazz++;
classes.push(name);
}
s += createClassLiteral(recurmax, stmtDepth, canThrow, name);
classes.length = class_len;
VAR_NAMES.length = nameLenBefore;
s += createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE);
return s;
}
case p++: case p++:
case p++: case p++:
case p++: case p++:
@ -1391,7 +1444,7 @@ function createArrayLiteral(recurmax, stmtDepth, canThrow) {
case 0: case 0:
case 1: case 1:
var name = getVarName(); var name = getVarName();
if (canThrow && rng(8) === 0) { if (canThrow && rng(20) == 0) {
arr.push("..." + name); arr.push("..." + name);
} else { } else {
arr.push('...("" + ' + name + ")"); arr.push('...("" + ' + name + ")");
@ -1475,14 +1528,33 @@ function createObjectKey(recurmax, stmtDepth, canThrow) {
return KEYS[rng(KEYS.length)]; return KEYS[rng(KEYS.length)];
} }
function createObjectFunction(recurmax, stmtDepth, canThrow) { function createSuperAssignment(recurmax, stmtDepth, canThrow) {
var s = rng(2) ? "super." + getDotKey() : "super[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]";
return getVarName(NO_CONST, NO_LAMBDA) + createAssignment() + s + ";";
}
function createThisAssignment(recurmax, stmtDepth, canThrow) {
var s = rng(2) ? "this." + getDotKey(true) : "this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]";
return s + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";";
}
function createObjectFunction(recurmax, stmtDepth, canThrow, internal, isClazz) {
var nameLenBefore = VAR_NAMES.length; var nameLenBefore = VAR_NAMES.length;
var save_async = async; var save_async = async;
var save_generator = generator; var save_generator = generator;
var s; var s;
var name = createObjectKey(recurmax, stmtDepth, canThrow); var name;
if (internal) {
name = internal;
} else if (isClazz) {
var clazzName = classes.pop();
name = createObjectKey(recurmax, stmtDepth, canThrow);
classes.push(clazzName);
} else {
name = createObjectKey(recurmax, stmtDepth, canThrow);
}
var fn; var fn;
switch (rng(SUPPORT.computed_key ? 3 : 2)) { switch (internal ? 2 : rng(SUPPORT.computed_key ? 3 : 2)) {
case 0: case 0:
async = false; async = false;
generator = false; generator = false;
@ -1493,7 +1565,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
defns(), defns(),
_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC), createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC),
"},", "}",
]; ];
}; };
break; break;
@ -1511,18 +1583,24 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
defns(), defns(),
_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";", "this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";",
"},", "}",
]; ];
}; };
break; break;
default: default:
async = SUPPORT.async && rng(50) == 0; if (/^(constructor|super)$/.test(internal)) {
generator = SUPPORT.generator && rng(50) == 0; async = false;
if (async && generator && !SUPPORT.async_generator) { generator = false;
if (rng(2)) { name = "constructor";
async = false; } else {
} else { async = SUPPORT.async && rng(50) == 0;
generator = false; generator = SUPPORT.generator && rng(50) == 0;
if (async && generator && !SUPPORT.async_generator) {
if (rng(2)) {
async = false;
} else {
generator = false;
}
} }
} }
fn = function(defns) { fn = function(defns) {
@ -1532,9 +1610,13 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) {
name + "(" + createParams(save_async, save_generator, NO_DUPLICATE) + "){", name + "(" + createParams(save_async, save_generator, NO_DUPLICATE) + "){",
strictMode(), strictMode(),
defns(), defns(),
_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), ];
"},", s.push(_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, stmtDepth));
] if (internal == "super") s.push("super" + createArgs(recurmax, stmtDepth, canThrow, NO_TEMPLATE) + ";");
if (/^(constructor|super)$/.test(internal) || rng(10) == 0) for (var i = rng(4); --i >= 0;) {
s.push(rng(2) ? createSuperAssignment(recurmax, stmtDepth, canThrow) : createThisAssignment(recurmax, stmtDepth, canThrow));
}
s.push(_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), "}");
}; };
break; break;
} }
@ -1561,7 +1643,7 @@ function createObjectLiteral(recurmax, stmtDepth, canThrow) {
obj.push(getVarName() + ","); obj.push(getVarName() + ",");
break; break;
case 4: case 4:
obj.push(createObjectFunction(recurmax, stmtDepth, canThrow)); obj.push(createObjectFunction(recurmax, stmtDepth, canThrow) + ",");
break; break;
default: default:
obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ": " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ","); obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ": " + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ",");
@ -1571,6 +1653,63 @@ function createObjectLiteral(recurmax, stmtDepth, canThrow) {
return obj.join("\n"); return obj.join("\n");
} }
function createClassLiteral(recurmax, stmtDepth, canThrow, name) {
recurmax--;
var save_async = async;
var save_generator = generator;
in_class++;
var s = "class", constructor = "constructor";
var isClazz = /^C/.test(name);
if (name) s += " " + name;
if (rng(10) == 0) {
constructor = "super";
s += " extends ";
var p = getVarName();
if (canThrow && rng(20) == 0) {
s += p;
} else {
s += "(" + p + " && " + p + ".constructor === Function ? " + p + " : function() {})";
}
}
s += " {\n";
var declared = [];
for (var i = rng(6); --i >= 0;) {
var fixed = false;
if (rng(5) == 0) {
fixed = true;
s += "static ";
}
var internal = null;
if (SUPPORT.class_private && rng(10) == 0) {
do {
internal = "#" + getDotKey();
} while (declared.indexOf(internal) >= 0);
declared.push(internal);
}
if (SUPPORT.class_field && rng(2)) {
s += internal || createObjectKey(recurmax, stmtDepth, canThrow);
if (rng(5)) {
async = false;
generator = false;
s += " = " + createExpression(recurmax, NO_COMMA, stmtDepth, fixed ? canThrow : CANNOT_THROW);
generator = save_generator;
async = save_async;
}
s += ";\n";
} else {
if (!fixed && !internal && constructor && rng(10) == 0) {
internal = constructor;
constructor = null;
}
s += createObjectFunction(recurmax, stmtDepth, canThrow, internal, isClazz) + "\n";
}
}
in_class--;
generator = save_generator;
async = save_async;
return s + "}";
}
function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { function createNestedBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it
return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow); return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
@ -1589,7 +1728,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
case 1: case 1:
return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))"; return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))";
case 2: case 2:
assignee = getVarName(NO_CONST); assignee = getVarName(NO_CONST, NO_LAMBDA);
return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")";
case 3: case 3:
assignee = getVarName(); assignee = getVarName();
@ -1627,7 +1766,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
].join(""); ].join("");
break; break;
} }
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")"; if (in_class) return "(Object.isExtensible(" + assignee + ") && " + expr + ")";
return canThrow && rng(20) == 0 ? expr : "(" + assignee + " && " + expr + ")";
case 4: case 4:
assignee = getVarName(); assignee = getVarName();
switch (SUPPORT.destructuring ? rng(20) : 2) { switch (SUPPORT.destructuring ? rng(20) : 2) {
@ -1664,7 +1804,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) {
].join(""); ].join("");
break; break;
} }
return canThrow && rng(10) == 0 ? expr : "(" + assignee + " && " + expr + ")"; if (in_class) return "(Object.isExtensible(" + assignee + ") && " + expr + ")";
return canThrow && rng(20) == 0 ? expr : "(" + assignee + " && " + expr + ")";
default: default:
return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow); return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow);
} }
@ -1715,11 +1856,19 @@ function createAssignment() {
} }
function createUnarySafePrefix() { function createUnarySafePrefix() {
return UNARY_SAFE[rng(UNARY_SAFE.length)]; var prefix;
do {
prefix = UNARY_SAFE[rng(UNARY_SAFE.length)];
} while (prefix == "delete " && in_class);
return prefix;
} }
function createUnaryPrefix() { function createUnaryPrefix() {
return UNARY_PREFIX[rng(UNARY_PREFIX.length)]; var prefix;
do {
prefix = UNARY_PREFIX[rng(UNARY_PREFIX.length)];
} while (prefix == "delete " && in_class);
return prefix;
} }
function createUnaryPostfix() { function createUnaryPostfix() {
@ -1735,7 +1884,18 @@ function removeAvoidVar(name) {
if (index >= 0) avoid_vars.splice(index, 1); if (index >= 0) avoid_vars.splice(index, 1);
} }
function getVarName(noConst) { function isBannedKeyword(name) {
switch (name) {
case "arguments":
return in_class;
case "await":
return async;
case "yield":
return generator || in_class;
}
}
function getVarName(noConst, noLambda) {
// try to get a generated name reachable from current scope. default to just `a` // try to get a generated name reachable from current scope. default to just `a`
var name, tries = 10; var name, tries = 10;
do { do {
@ -1743,9 +1903,14 @@ function getVarName(noConst) {
name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)]; name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)];
} while (!name } while (!name
|| avoid_vars.indexOf(name) >= 0 || avoid_vars.indexOf(name) >= 0
|| noConst && block_vars.indexOf(name) >= 0 || noConst && (block_vars.indexOf(name) >= 0
|| async && name == "await" || in_class && [
|| generator && name == "yield"); "Infinity",
"NaN",
"undefined",
].indexOf(name) >= 0)
|| noLambda && lambda_vars.indexOf(name) >= 0
|| isBannedKeyword(name));
return name; return name;
} }
@ -1759,8 +1924,7 @@ function createVarName(maybe, dontStore) {
if (suffix) name += "_" + suffix; if (suffix) name += "_" + suffix;
} while (unique_vars.indexOf(name) >= 0 } while (unique_vars.indexOf(name) >= 0
|| block_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0
|| async && name == "await" || isBannedKeyword(name));
|| generator && name == "yield");
if (!dontStore) VAR_NAMES.push(name); if (!dontStore) VAR_NAMES.push(name);
return name; return name;
} }
@ -2006,6 +2170,16 @@ function is_error_destructuring(ex) {
return ex.name == "TypeError" && /^Cannot destructure /.test(ex.message); return ex.name == "TypeError" && /^Cannot destructure /.test(ex.message);
} }
function is_error_class_constructor(ex) {
return ex.name == "TypeError" && /\bconstructors?\b/.test(ex.message) && /\bnew\b/.test(ex.message);
}
function is_error_getter_only_property(ex) {
return ex.name == "TypeError" && [ "getter", "only", "property" ].every(function(keyword) {
return ex.message.indexOf(keyword) >= 0;
});
}
function patch_try_catch(orig, toplevel) { function patch_try_catch(orig, toplevel) {
var stack = [ { var stack = [ {
code: orig, code: orig,
@ -2067,6 +2241,12 @@ function patch_try_catch(orig, toplevel) {
} else if (is_error_destructuring(result)) { } else if (is_error_destructuring(result)) {
index = result.ufuzz_catch; index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index); return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index);
} else if (is_error_class_constructor(result)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("missing new for class");' + orig.slice(index);
} else if (is_error_getter_only_property(result)) {
index = result.ufuzz_catch;
return orig.slice(0, index) + result.ufuzz_var + ' = new Error("setting getter-only property");' + orig.slice(index);
} }
} }
stack.filled = true; stack.filled = true;
@ -2168,6 +2348,16 @@ for (var round = 1; round <= num_iterations; round++) {
} }
// ignore difference in error message caused by Temporal Dead Zone // ignore difference in error message caused by Temporal Dead Zone
if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true; if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true;
// ignore difference due to implicit strict-mode in `class`
if (!ok && /\bclass\b/.test(original_code)) {
var original_strict = sandbox.run_code('"use strict";' + original_code, toplevel);
var uglify_strict = sandbox.run_code('"use strict";' + uglify_code, toplevel);
if (typeof original_strict != "string" && /strict/.test(original_strict.message)) {
ok = typeof uglify_strict != "string" && /strict/.test(uglify_strict.message);
} else {
ok = sandbox.same_stdout(original_strict, uglify_strict);
}
}
// ignore difference in error message caused by `in` // ignore difference in error message caused by `in`
if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true; if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true;
// ignore difference in error message caused by spread syntax // ignore difference in error message caused by spread syntax
@ -2180,6 +2370,14 @@ for (var round = 1; round <= num_iterations; round++) {
if (!ok && errored && is_error_destructuring(uglify_result) && is_error_destructuring(original_result)) { if (!ok && errored && is_error_destructuring(uglify_result) && is_error_destructuring(original_result)) {
ok = true; ok = true;
} }
// ignore difference in error message caused by call on class
if (!ok && errored && is_error_class_constructor(uglify_result) && is_error_class_constructor(original_result)) {
ok = true;
}
// ignore difference in error message caused by setting getter-only property
if (!ok && errored && is_error_getter_only_property(uglify_result) && is_error_getter_only_property(original_result)) {
ok = true;
}
// ignore errors above when caught by try-catch // ignore errors above when caught by try-catch
if (!ok) { if (!ok) {
var orig_skipped = patch_try_catch(original_code, toplevel); var orig_skipped = patch_try_catch(original_code, toplevel);

View File

@ -1,4 +1,5 @@
exports["Dictionary"] = Dictionary; exports["Dictionary"] = Dictionary;
exports["is_statement"] = is_statement;
exports["List"] = List; exports["List"] = List;
exports["minify"] = minify; exports["minify"] = minify;
exports["parse"] = parse; exports["parse"] = parse;