diff options
-rw-r--r-- | lib/ast.js | 207 | ||||
-rw-r--r-- | lib/compress.js | 253 | ||||
-rw-r--r-- | lib/output.js | 91 | ||||
-rw-r--r-- | lib/parse.js | 188 | ||||
-rw-r--r-- | lib/scope.js | 13 | ||||
-rw-r--r-- | lib/transform.js | 9 | ||||
-rw-r--r-- | test/compress/classes.js | 574 | ||||
-rw-r--r-- | test/compress/conditionals.js | 36 | ||||
-rw-r--r-- | test/compress/exports.js | 44 | ||||
-rw-r--r-- | test/compress/hoist_props.js | 26 | ||||
-rw-r--r-- | test/compress/imports.js | 21 | ||||
-rw-r--r-- | test/compress/objects.js | 66 | ||||
-rw-r--r-- | test/compress/properties.js | 22 | ||||
-rw-r--r-- | test/exports.js | 1 | ||||
-rw-r--r-- | test/reduce.js | 14 | ||||
-rw-r--r-- | test/sandbox.js | 8 | ||||
-rw-r--r-- | test/ufuzz/index.js | 298 | ||||
-rw-r--r-- | tools/exports.js | 1 |
18 files changed, 1699 insertions, 173 deletions
@@ -217,6 +217,12 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, { $documentation: "The empty statement (empty block or simply a semicolon)" }, 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) { multiple = multiple ? "contain" : "be"; 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_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_Statement && !(value instanceof AST_LambdaExpression)) { - throw new Error(prop + " cannot " + multiple + " AST_Statement"); - } + if (is_statement(value)) throw new Error(prop + " cannot " + multiple + " AST_Statement"); if (value instanceof AST_SymbolDeclaration) { throw new Error(prop + " cannot " + multiple + " AST_SymbolDeclaration"); } @@ -301,8 +305,7 @@ var AST_Block = DEFNODE("Block", "body", { _validate: function() { if (this.TYPE == "Block") throw new Error("should not instantiate AST_Block"); this.body.forEach(function(node) { - if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]"); - if (node instanceof AST_LambdaExpression) throw new Error("body cannot contain AST_LambdaExpression"); + if (!is_statement(node)) throw new Error("body must contain AST_Statement"); }); }, }, AST_BlockScope); @@ -318,8 +321,7 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { }, _validate: function() { 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 (this.body instanceof AST_LambdaExpression) throw new Error("body cannot be AST_LambdaExpression"); + if (!is_statement(this.body)) throw new Error("body must be AST_Statement"); }, }, AST_BlockScope); @@ -416,8 +418,7 @@ var AST_For = DEFNODE("For", "init condition step", { _validate: function() { if (this.init != null) { if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node"); - if (this.init instanceof AST_Statement - && !(this.init instanceof AST_Definitions || this.init instanceof AST_LambdaExpression)) { + if (is_statement(this.init) && !(this.init instanceof AST_Definitions)) { throw new Error("init cannot be AST_Statement"); } } @@ -699,7 +700,7 @@ var AST_AsyncArrow = DEFNODE("AsyncArrow", "value", { var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", { $documentation: "An asynchronous function expression", $propdoc: { - name: "[AST_SymbolLambda?] the name of this function", + name: "[AST_SymbolLambda?] the name of this function, or null if not specified", }, _validate: function() { if (this.name != null) { @@ -711,7 +712,7 @@ var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", { var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", { $documentation: "An asynchronous generator function expression", $propdoc: { - name: "[AST_SymbolLambda?] the name of this function", + name: "[AST_SymbolLambda?] the name of this function, or null if not specified", }, _validate: function() { if (this.name != null) { @@ -723,7 +724,7 @@ var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", { var AST_Function = DEFNODE("Function", "name", { $documentation: "A function expression", $propdoc: { - name: "[AST_SymbolLambda?] the name of this function", + name: "[AST_SymbolLambda?] the name of this function, or null if not specified", }, _validate: function() { if (this.name != null) { @@ -735,7 +736,7 @@ var AST_Function = DEFNODE("Function", "name", { var AST_GeneratorFunction = DEFNODE("GeneratorFunction", "name", { $documentation: "A generator function expression", $propdoc: { - name: "[AST_SymbolLambda?] the name of this function", + name: "[AST_SymbolLambda?] the name of this function, or null if not specified", }, _validate: function() { if (this.name != null) { @@ -772,6 +773,111 @@ var AST_GeneratorDefun = DEFNODE("GeneratorDefun", null, { $documentation: "A generator function definition", }, 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 ]----- */ var AST_Jump = DEFNODE("Jump", null, { @@ -857,8 +963,7 @@ var AST_If = DEFNODE("If", "condition alternative", { _validate: function() { must_be_expression(this, "condition"); if (this.alternative != null) { - if (!(this.alternative instanceof AST_Statement)) throw new Error("alternative must be AST_Statement"); - if (this.alternative instanceof AST_LambdaExpression) throw new error("alternative cannot be AST_LambdaExpression"); + if (!is_statement(this.alternative)) throw new Error("alternative must be AST_Statement"); } }, }, AST_StatementWithBody); @@ -1042,7 +1147,7 @@ var AST_VarDef = DEFNODE("VarDef", "name value", { var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", { $documentation: "An `export` statement", $propdoc: { - body: "[AST_Definitions|AST_LambdaDefinition] the statement to export", + body: "[AST_DefClass|AST_Definitions|AST_LambdaDefinition] the statement to export", }, walk: function(visitor) { var node = this; @@ -1051,8 +1156,10 @@ var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", { }); }, _validate: function() { - if (!(this.body instanceof AST_Definitions || this.body instanceof AST_LambdaDefinition)) { - throw new Error("body must be AST_Definitions or AST_LambdaDefinition"); + if (!(this.body instanceof AST_DefClass + || 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); @@ -1069,8 +1176,14 @@ var AST_ExportDefault = DEFNODE("ExportDefault", "body", { }); }, _validate: function() { - if (this.body instanceof AST_Lambda && this.body.name) { - if (!(this.body instanceof AST_LambdaDefinition)) throw new Error("body must be AST_LambdaDefinition"); + if (this.body instanceof AST_Class && this.body.name) { + 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 { must_be_expression(this, "body"); } @@ -1574,6 +1687,13 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, { }, }, 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, { $documentation: "An object setter property", _validate: function() { @@ -1609,6 +1729,16 @@ var AST_SymbolConst = DEFNODE("SymbolConst", null, { $documentation: "Symbol defining a constant", }, 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, { $documentation: "Symbol defining a lexical-scoped variable", }, AST_SymbolDeclaration); @@ -1621,16 +1751,6 @@ var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { $documentation: "Symbol naming a function argument", }, 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, { $documentation: "Symbol defining a function", }, AST_SymbolDeclaration); @@ -1639,6 +1759,14 @@ var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { $documentation: "Symbol naming a function expression", }, 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, { $documentation: "Symbol naming the exception in catch", }, AST_SymbolDeclaration); @@ -1672,12 +1800,26 @@ var AST_LabelRef = DEFNODE("LabelRef", null, { $documentation: "Reference to a label 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, { $documentation: "The `this` symbol", _validate: function() { if (this.name !== "this") throw new Error('name must be "this"'); }, -}, AST_Symbol); +}, AST_ObjectIdentity); var AST_Template = DEFNODE("Template", "expressions strings tag", { $documentation: "A template literal, i.e. tag`str1${expr1}...strN${exprN}strN+1`", @@ -1837,7 +1979,8 @@ TreeWalker.prototype = { this.stack.push(node); }, 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); } }, diff --git a/lib/compress.js b/lib/compress.js index c16639fb..dcd48fa8 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -348,7 +348,7 @@ merge(Compressor.prototype, { var props = obj.properties; for (var i = props.length; --i >= 0;) { 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; } } @@ -600,7 +600,7 @@ merge(Compressor.prototype, { if (!value) return false; return value.is_constant() || value instanceof AST_Lambda - || value instanceof AST_This; + || value instanceof AST_ObjectIdentity; } function has_escaped(d, node, parent) { @@ -933,6 +933,36 @@ merge(Compressor.prototype, { 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) { this.condition.walk(tw); push(tw); @@ -1374,12 +1404,7 @@ merge(Compressor.prototype, { } function is_lhs_read_only(lhs, compressor) { - if (lhs instanceof AST_This) 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_ObjectIdentity) return true; if (lhs instanceof AST_PropAccess) { lhs = lhs.expression; if (lhs instanceof AST_SymbolRef) { @@ -1390,6 +1415,11 @@ merge(Compressor.prototype, { if (lhs.is_constant()) return true; 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; } @@ -1472,10 +1502,14 @@ merge(Compressor.prototype, { 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) { if (thing === null) return []; 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 ]; if (thing instanceof AST_EmptyStatement) return []; if (thing instanceof AST_Statement) return [ thing ]; @@ -1851,7 +1885,12 @@ merge(Compressor.prototype, { } 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 if (node instanceof AST_Scope) return node; // Stop upon collision with block-scoped variables @@ -1905,6 +1944,7 @@ merge(Compressor.prototype, { if (!lhs.equivalent_to(node.expression)) return false; 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_Defun) return funarg && lhs.name === node.name.name; if (node instanceof AST_DestructuredKeyVal) return node.key instanceof AST_Node; @@ -1990,6 +2030,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Function) { 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) { var exp = node.expression; 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); } 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 (check_destructured(node.name)) return true; return (node.value || parent instanceof AST_Let) && node.name.match_symbol(function(node) { @@ -2079,7 +2119,7 @@ merge(Compressor.prototype, { } 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; return true; } @@ -2473,12 +2513,12 @@ merge(Compressor.prototype, { expr.left.walk(marker); expr = expr.right; } + if (expr instanceof AST_ObjectIdentity) return rhs_exact_match; if (expr instanceof AST_SymbolRef) { var value = expr.evaluate(compressor); if (value === expr) return 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_constant()) { var ev = expr.evaluate(compressor); @@ -2519,7 +2559,7 @@ merge(Compressor.prototype, { if (!node) return true; } 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) { @@ -2531,7 +2571,7 @@ merge(Compressor.prototype, { var value; if (node instanceof AST_SymbolRef) { value = node.fixed_value() || node; - } else if (node instanceof AST_This) { + } else if (node instanceof AST_ObjectIdentity) { value = node; } if (value) lvalues.add(node.name, is_modified(compressor, tw, node, value, 0)); @@ -2681,7 +2721,7 @@ merge(Compressor.prototype, { var stat = statements[i]; if (stat instanceof AST_BlockStatement) { if (all(stat.body, function(stat) { - return !(stat instanceof AST_Const || stat instanceof AST_Let); + return !is_lexical_definition(stat); })) { CHANGED = true; eliminate_spurious_blocks(stat.body); @@ -3025,7 +3065,7 @@ merge(Compressor.prototype, { var line = block.body[i]; if (line instanceof AST_Var && declarations_only(line)) { decls.push(line); - } else if (stat || line instanceof AST_Const || line instanceof AST_Let) { + } else if (stat || is_lexical_definition(line)) { return false; } else { stat = line; @@ -3387,7 +3427,7 @@ merge(Compressor.prototype, { function push(node) { if (block) { block.push(node); - if (node instanceof AST_Const || node instanceof AST_Let) block.required = true; + if (is_lexical_definition(node)) block.required = true; } else { target.push(node); } @@ -3531,6 +3571,9 @@ merge(Compressor.prototype, { return prop instanceof AST_ObjectKeyVal; }); }); + def(AST_ObjectIdentity, function(compressor) { + return is_strict(compressor) && !this.scope.new; + }); def(AST_Sequence, function(compressor) { return this.tail_node()._dot_throw(compressor); }); @@ -3553,9 +3596,6 @@ merge(Compressor.prototype, { this._dot_throw = return_false; return false; }); - def(AST_This, function(compressor) { - return is_strict(compressor) && !this.scope.new; - }); def(AST_UnaryPrefix, function() { return this.operator == "void"; }); @@ -4077,6 +4117,7 @@ merge(Compressor.prototype, { }); def(AST_Accessor, return_this); def(AST_BigInt, return_this); + def(AST_Class, return_this); def(AST_Node, return_this); def(AST_Constant, function() { return this.value; @@ -4595,6 +4636,9 @@ merge(Compressor.prototype, { } return basic_negation(this); }); + def(AST_ClassExpression, function() { + return basic_negation(this); + }); def(AST_Conditional, function(compressor, first_in_statement) { var self = this.clone(); self.consequent = self.consequent.negate(compressor); @@ -4673,7 +4717,7 @@ merge(Compressor.prototype, { || exp instanceof AST_Object && all(exp.properties, function(prop) { return !(prop instanceof AST_ObjectGetter || prop instanceof AST_Spread); }) - || exp instanceof AST_This + || exp instanceof AST_ObjectIdentity || exp instanceof AST_Unary); } @@ -4697,7 +4741,7 @@ merge(Compressor.prototype, { var lhs = this.left; if (!(lhs instanceof AST_PropAccess)) return true; var node = lhs.expression; - return !(node instanceof AST_This) + return !(node instanceof AST_ObjectIdentity) || !node.scope.new || lhs instanceof AST_Sub && lhs.property.has_side_effects(compressor) || this.right.has_side_effects(compressor); @@ -4721,6 +4765,13 @@ merge(Compressor.prototype, { return this.expression.has_side_effects(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) { return this.condition.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); }); }); + def(AST_ObjectIdentity, return_false); def(AST_ObjectProperty, function(compressor) { return this.key instanceof AST_Node && this.key.has_side_effects(compressor) || this.value.has_side_effects(compressor); @@ -4786,7 +4838,6 @@ merge(Compressor.prototype, { def(AST_Template, function(compressor) { return this.tag && !is_raw_tag(compressor, this.tag) || any(this.expressions, compressor); }); - def(AST_This, return_false); def(AST_Try, function(compressor) { return any(this.body, compressor) || this.bcatch && this.bcatch.has_side_effects(compressor) @@ -4810,8 +4861,8 @@ merge(Compressor.prototype, { def(AST_Constant, return_false); def(AST_EmptyStatement, return_false); def(AST_Lambda, return_false); + def(AST_ObjectIdentity, return_false); def(AST_SymbolDeclaration, return_false); - def(AST_This, return_false); function any(list, compressor) { for (var i = list.length; --i >= 0;) @@ -4933,6 +4984,12 @@ merge(Compressor.prototype, { && this.right.is_constant_expression() && (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_Lambda, function(scope) { var self = this; @@ -4965,7 +5022,7 @@ merge(Compressor.prototype, { result = false; return true; } - if (node instanceof AST_This) { + if (node instanceof AST_ObjectIdentity) { if (scopes.length == 0 && is_arrow(self)) result = false; return true; } @@ -5042,7 +5099,7 @@ merge(Compressor.prototype, { return in_list ? List.skip : make_node(AST_EmptyStatement, node); case 1: var stat = node.body[0]; - if (stat instanceof AST_Const || stat instanceof AST_Let) return node; + if (is_lexical_definition(stat)) return node; if (parent instanceof AST_IterationStatement && stat instanceof AST_LambdaDefinition) return node; return stat; } @@ -5347,6 +5404,10 @@ merge(Compressor.prototype, { }); return true; } + if (node instanceof AST_SymbolConst || node instanceof AST_SymbolLet) { + references[node.definition().id] = false; + return true; + } if (node instanceof AST_SymbolRef) { mark(node, 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) { var ctor; switch (defun.CTOR) { @@ -5658,6 +5725,25 @@ merge(Compressor.prototype, { } if (node === self) return; 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) { var def = node.name.definition(); 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 (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 (drop_funcs && node !== self && node instanceof AST_LambdaDefinition) { var def = node.name.definition(); if (!(def.id in in_use_ids)) { log(node.name, "Dropping unused function {name}"); 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); } } @@ -6151,16 +6256,20 @@ merge(Compressor.prototype, { return node; } if (node instanceof AST_Scope) { - var save_scope = scope; - scope = node; - descend(node, tt); - scope = save_scope; + descend_scope(); return node; } if (node instanceof AST_SymbolImport) { if (!compressor.option("imports") || node.definition().id in in_use_ids) return node; 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) { if (node instanceof AST_BlockStatement) { return trim_block(node, tt.parent(), in_list); @@ -6740,6 +6849,7 @@ merge(Compressor.prototype, { if (!compressor.option("booleans")) return; var bool_returns = map_bool_returns(this); if (!all_bool(this.name.definition(), bool_returns, compressor)) return; + if (compressor.parent() instanceof AST_ExportDefault) return; process_boolean_returns(this, compressor); }); AST_Function.DEFMETHOD("process_boolean_returns", function(compressor) { @@ -6915,9 +7025,7 @@ merge(Compressor.prototype, { if (sym.fixed_value() !== right) return; return right instanceof AST_Object && right.properties.length > 0 - && all(right.properties, function(prop) { - return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string"; - }) + && all(right.properties, can_hoist_property) && all(def.references, function(ref) { return ref.fixed_value() === right; }) @@ -6943,7 +7051,6 @@ merge(Compressor.prototype, { // returned if nothing changed. function trim(nodes, compressor, first_in_statement, spread) { var len = nodes.length; - if (!len) return null; var ret = [], changed = false; for (var i = 0; i < len; i++) { var node = nodes[i]; @@ -6959,7 +7066,7 @@ merge(Compressor.prototype, { 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) { var exp = node.expression; @@ -7106,6 +7213,33 @@ merge(Compressor.prototype, { } 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) { var consequent = this.consequent.drop_side_effect_free(compressor); var alternative = this.alternative.drop_side_effect_free(compressor); @@ -7178,6 +7312,7 @@ merge(Compressor.prototype, { }) : node; })); }); + def(AST_ObjectIdentity, return_null); def(AST_Sequence, function(compressor, first_in_statement) { var expressions = trim(this.expressions, compressor, first_in_statement); if (!expressions) return null; @@ -7220,7 +7355,6 @@ merge(Compressor.prototype, { if (expressions.length == 0) return null; 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) { var exp = this.expression; if (unary_side_effects[this.operator]) { @@ -7485,7 +7619,7 @@ merge(Compressor.prototype, { }); 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; if ((name instanceof AST_Destructured || name instanceof AST_SymbolLet) && !name.match_symbol(function(node) { @@ -10009,6 +10143,7 @@ merge(Compressor.prototype, { def.single_use = false; fixed._squeezed = 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_Lambda) { var scope = self.scope.resolve(); @@ -10719,20 +10854,21 @@ merge(Compressor.prototype, { || alternative.expression instanceof AST_PropAccess) || is_tail_equivalent(consequent.expression, alternative.expression); } - if (consequent instanceof AST_Dot) return consequent.property == alternative.property; - if (consequent instanceof AST_Sub) return consequent.property.equivalent_to(alternative.property); + if (!(consequent instanceof AST_PropAccess)) return; + 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) { if (!is_tail_equivalent(consequent, alternative)) return !top && make_node(AST_Conditional, self, { condition: condition, consequent: consequent, - alternative: alternative + alternative: alternative, }); - var exp = combine_tail(consequent.expression, alternative.expression); - if (!exp) return; var node = consequent.clone(); - node.expression = exp; + node.expression = combine_tail(consequent.expression, alternative.expression); 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_AsyncArrow.DEFMETHOD("contains_this", return_false); AST_Scope.DEFMETHOD("contains_this", function() { @@ -10973,6 +11124,12 @@ merge(Compressor.prototype, { 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) { if (!compressor.option("properties")) return; var expr = this.expression; @@ -10981,9 +11138,7 @@ merge(Compressor.prototype, { for (var i = props.length; --i >= 0;) { var prop = props[i]; if (prop.key != key) continue; - if (!all(props, function(prop) { - return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string"; - })) break; + if (!all(props, can_hoist_property)) break; if (!safe_to_flatten(prop.value, compressor)) break; return make_node(AST_Sub, this, { expression: make_node(AST_Array, expr, { @@ -11145,7 +11300,7 @@ merge(Compressor.prototype, { 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(); keys.add(key, prop); } else { diff --git a/lib/output.js b/lib/output.js index 29c85a42..592717b0 100644 --- a/lib/output.js +++ b/lib/output.js @@ -671,6 +671,7 @@ function OutputStream(options) { } PARENS(AST_AsyncFunction, needs_parens_function); PARENS(AST_AsyncGeneratorFunction, needs_parens_function); + PARENS(AST_ClassExpression, needs_parens_function); PARENS(AST_Function, needs_parens_function); PARENS(AST_GeneratorFunction, needs_parens_function); @@ -688,6 +689,9 @@ function OutputStream(options) { // (await x)(y) // new (await x) 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] // (typeof x).y if (p instanceof AST_PropAccess) return p.expression === this; @@ -707,6 +711,12 @@ function OutputStream(options) { || p instanceof AST_Binary // new (foo, bar) or foo(1, (2, 3), 4) || 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) // ---> 20 (side effect, set a := 10 and b := 20) || p instanceof AST_Conditional @@ -747,6 +757,9 @@ function OutputStream(options) { } // (foo && bar)() 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 if (p instanceof AST_PropAccess) return p.expression === this; // typeof (foo && bar) @@ -810,6 +823,9 @@ function OutputStream(options) { if (p instanceof AST_Binary) return !(p instanceof AST_Assign); // (a = func)() —or— new (a = Object)() 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 if (p instanceof AST_Conditional) return p.condition === self; // (a = foo)["prop"] —or— (a = foo).prop @@ -1015,7 +1031,9 @@ function OutputStream(options) { output.print("default"); output.space(); 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) { var self = this; @@ -1157,6 +1175,59 @@ function OutputStream(options) { DEFPRINT(AST_GeneratorDefun, 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 ]----- */ function print_jump(kind, prop) { return function(output) { @@ -1526,12 +1597,12 @@ function OutputStream(options) { }); else print_braced_empty(this, output); }); - function print_properties(self, output) { + function print_properties(self, output, no_comma) { var props = self.properties; if (props.length > 0) output.with_block(function() { props.forEach(function(prop, i) { if (i) { - output.print(","); + if (!no_comma) output.print(","); output.newline(); } output.indent(); @@ -1557,7 +1628,9 @@ function OutputStream(options) { output.print(make_num(key)); } else { 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")) { output.print_string(key, quote); } else { @@ -1576,9 +1649,16 @@ function OutputStream(options) { self.value.print(output); } DEFPRINT(AST_ObjectKeyVal, print_key_value); + DEFPRINT(AST_ObjectMethod, function(output) { + print_method(this, output); + }); function print_accessor(type) { return function(output) { var self = this; + if (self.static) { + output.print("static"); + output.space(); + } output.print(type); output.space(); print_property_key(self, output); @@ -1615,9 +1695,6 @@ function OutputStream(options) { print_symbol(self, output); }); DEFPRINT(AST_Hole, noop); - DEFPRINT(AST_This, function(output) { - output.print("this"); - }); DEFPRINT(AST_Template, function(output) { var self = this; if (self.tag) self.tag.print(output); diff --git a/lib/parse.js b/lib/parse.js index 6106834b..26c618ef 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,10 +44,10 @@ "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 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, ].join(" "); @@ -886,6 +886,10 @@ function parse($TEXT, options) { next(); return break_cont(AST_Break); + case "class": + next(); + return class_(AST_DefClass); + case "const": next(); var node = const_(); @@ -1047,6 +1051,128 @@ function parse($TEXT, options) { 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_() { var await = is("name", "await") && next(); expect("("); @@ -1365,13 +1491,12 @@ function parse($TEXT, options) { return new AST_ExportDeclaration({ body: export_decl() }); } - function maybe_named(def, exp) { - var node = function_(exp); - if (node.name) { - node = new def(node); - node.name = new AST_SymbolDefun(node.name); + function maybe_named(def, expr) { + if (expr.name) { + expr = new def(expr); + expr.name = new (def === AST_DefClass ? AST_SymbolDefClass : AST_SymbolDefun)(expr.name); } - return node; + return expr; } function export_default_decl() { @@ -1380,14 +1505,17 @@ function parse($TEXT, options) { if (!is_token(peek(), "keyword", "function")) return; next(); next(); - if (!is("operator", "*")) return maybe_named(AST_AsyncDefun, AST_AsyncFunction); + if (!is("operator", "*")) return maybe_named(AST_AsyncDefun, function_(AST_AsyncFunction)); 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": next(); - if (!is("operator", "*")) return maybe_named(AST_Defun, AST_Function); + if (!is("operator", "*")) return maybe_named(AST_Defun, function_(AST_Function)); 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); next(); return function_(AST_AsyncGeneratorDefun); + case "class": + next(); + return class_(AST_DefClass); case "const": next(); var node = const_(); @@ -1706,7 +1837,14 @@ function parse($TEXT, options) { } 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(); var func; if (is("operator", "*")) { @@ -1813,7 +1951,7 @@ function parse($TEXT, options) { var gen = function_(AST_GeneratorFunction); gen.start = gen_start; gen.end = prev(); - a.push(new AST_ObjectKeyVal({ + a.push(new AST_ObjectMethod({ start: start, key: key, value: gen, @@ -1862,7 +2000,7 @@ function parse($TEXT, options) { var func = function_(AST_Function); func.start = func_start; func.end = prev(); - a.push(new AST_ObjectKeyVal({ + a.push(new AST_ObjectMethod({ start: start, key: key, value: func, @@ -1888,7 +2026,7 @@ function parse($TEXT, options) { var func = function_(is_gen ? AST_AsyncGeneratorFunction : AST_AsyncFunction); func.start = func_start; func.end = prev(); - a.push(new AST_ObjectKeyVal({ + a.push(new AST_ObjectMethod({ start: start, key: key, value: func, @@ -1948,9 +2086,21 @@ function parse($TEXT, options) { function _make_symbol(type, token) { var name = token.value; - if (name === "await" && S.in_async) unexpected(token); - if (name === "yield" && S.in_generator) unexpected(token); - return new (name === "this" ? AST_This : type)({ + switch (name) { + case "await": + 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, start: token, end: token, diff --git a/lib/scope.js b/lib/scope.js index 97027274..799b0dc6 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -124,6 +124,19 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { var next_def_id = 0; var scope = self.parent_scope = null; var tw = new TreeWalker(function(node, descend) { + if (node instanceof AST_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) { var save_exported = exported; exported = tw.parent() instanceof AST_ExportDeclaration; diff --git a/lib/transform.js b/lib/transform.js index bc08436e..dcf90dfa 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -147,6 +147,15 @@ TreeTransformer.prototype = new TreeWalker; } DEF(AST_Arrow, 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) { self.expression = self.expression.transform(tw); self.args = do_list(self.args, tw); diff --git a/test/compress/classes.js b/test/compress/classes.js new file mode 100644 index 00000000..8c5575c0 --- /dev/null +++ b/test/compress/classes.js @@ -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" +} diff --git a/test/compress/conditionals.js b/test/compress/conditionals.js index 64088eb2..4187c152 100644 --- a/test/compress/conditionals.js +++ b/test/compress/conditionals.js @@ -1861,3 +1861,39 @@ issue_3808_2: { } 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" +} diff --git a/test/compress/exports.js b/test/compress/exports.js index 24c1d8c0..d3a4ffce 100644 --- a/test/compress/exports.js +++ b/test/compress/exports.js @@ -17,22 +17,25 @@ var_defs: { defuns: { input: { + export class A {} export function e() {} export function* f(a) {} export async function g(b, c) {} 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: { input: { 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 }, ...[ 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: { @@ -88,18 +91,22 @@ drop_unused: { input: { export default 42; export default (x, y) => x * x; - export default function*(a, b) {}; - export default async function f({ c }, ...[ d ]) {}; + export default class A extends B { get p() { h() } } + export default function*(a, b) {} + export default async function f({ c }, ...[ d ]) {} export var e; export function g(x, [ y ], ...z) {} + function h() {} } expect: { export default 42; export default (x, y) => x * x; - export default function*(a, b) {}; - export default async function({}) {}; + export default class extends B { get p() { h() } } + export default function*(a, b) {} + export default async function({}) {} export var e; export function g(x, []) {} + function h() {} } } @@ -195,3 +202,28 @@ hoist_exports: { 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; + } + } +} diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index dbee9b42..597db4bc 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -1068,3 +1068,29 @@ issue_4023: { } 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" +} diff --git a/test/compress/imports.js b/test/compress/imports.js index f8313b06..558dd6a8 100644 --- a/test/compress/imports.js +++ b/test/compress/imports.js @@ -144,3 +144,24 @@ keep_ref: { 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(); + } +} diff --git a/test/compress/objects.js b/test/compress/objects.js index 67acefd6..3c7f2d18 100644 --- a/test/compress/objects.js +++ b/test/compress/objects.js @@ -280,6 +280,72 @@ shorthand_keywords: { 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: { options = { evaluate: true, diff --git a/test/compress/properties.js b/test/compress/properties.js index 43f08b42..eb47624a 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -1378,3 +1378,25 @@ issue_3389: { } 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" +} diff --git a/test/exports.js b/test/exports.js index fd1237f3..9cebfaaf 100644 --- a/test/exports.js +++ b/test/exports.js @@ -1,5 +1,6 @@ exports["Compressor"] = Compressor; exports["defaults"] = defaults; +exports["is_statement"] = is_statement; exports["JS_Parse_Error"] = JS_Parse_Error; exports["List"] = List; exports["mangle_properties"] = mangle_properties; diff --git a/test/reduce.js b/test/reduce.js index 4d628aa2..25dc14ae 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -181,7 +181,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } else if (node instanceof U.AST_Call) { var expr = [ - node.expression, + !(node.expression instanceof U.AST_Super) && node.expression, node.args[0], null, // intentional ][ ((node.start._permute += step) * steps | 0) % 3 ]; @@ -202,7 +202,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) var seq = []; body.forEach(function(node) { 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 seq.push(expr); } @@ -358,7 +358,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } else if (node instanceof U.AST_PropAccess) { 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.start._permute++ % 2 ]; if (expr) { @@ -468,7 +468,7 @@ module.exports = function reduce_test(testcase, minify_options, reduce_options) } // replace this node - var newNode = is_statement(node) ? new U.AST_EmptyStatement({ + var newNode = U.is_statement(node) ? new U.AST_EmptyStatement({ start: {}, }) : U.parse(REPLACEMENTS[node.start._permute % REPLACEMENTS.length | 0], { expression: true, @@ -666,10 +666,6 @@ function is_timed_out(result) { 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) { if (node instanceof U.AST_Sequence) { array.push.apply(array, node.expressions); @@ -689,7 +685,7 @@ function to_sequence(expressions) { } 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, start: {}, }); diff --git a/test/sandbox.js b/test/sandbox.js index d8d2da0d..1aee7753 100644 --- a/test/sandbox.js +++ b/test/sandbox.js @@ -25,7 +25,13 @@ exports.run_code = semver.satisfies(process.version, "0.8") ? function(code, top } while (prev !== stdout); return stdout; } : 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); } else { return run_code_vm(code, toplevel, timeout); diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 9df41929..0625a267 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -137,6 +137,9 @@ var SUPPORT = function(matrix) { async_generator: "async function* f(){}", bigint: "42n", catch_omit_var: "try {} catch {}", + class: "class C { f() {} }", + class_field: "class C { p = 0; }", + class_private: "class C { #f() {} }", computed_key: "({[0]: 0});", const_block: "var a; { const a = 0; }", default_value: "[ a = 0 ] = [];", @@ -312,6 +315,8 @@ var DEFUN_OK = true; var DONT_STORE = true; var NO_CONST = true; var NO_DUPLICATE = true; +var NO_LAMBDA = true; +var NO_TEMPLATE = true; var VAR_NAMES = [ "a", @@ -352,11 +357,15 @@ var TYPEOF_OUTCOMES = [ var avoid_vars = []; var block_vars = []; +var lambda_vars = []; var unique_vars = []; +var classes = []; var async = false; var generator = false; var loops = 0; var funcs = 0; +var clazz = 0; +var in_class = 0; var called = Object.create(null); var labels = 10000; @@ -372,11 +381,15 @@ function strictMode() { function createTopLevelCode() { VAR_NAMES.length = INITIAL_NAMES_LEN; // prune any previous names still in the list block_vars.length = 0; + lambda_vars.length = 0; unique_vars.length = 0; + classes.length = 0; async = false; generator = false; loops = 0; funcs = 0; + clazz = 0; + in_class = 0; called = Object.create(null); return [ strictMode(), @@ -411,7 +424,7 @@ function createParams(was_async, was_generator, noDuplicate) { var params = []; for (var n = rng(4); --n >= 0;) { var name = createVarName(MANDATORY); - if (noDuplicate) unique_vars.push(name); + if (noDuplicate || in_class) unique_vars.push(name); params.push(name); } unique_vars.length = len; @@ -428,7 +441,7 @@ function createArgs(recurmax, stmtDepth, canThrow, noTemplate) { case 0: case 1: var name = getVarName(); - if (canThrow && rng(8) === 0) { + if (canThrow && rng(20) == 0) { args.push("..." + name); } else { args.push('...("" + ' + name + ")"); @@ -457,9 +470,9 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was var side_effects = []; values.forEach(function(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); - } else if (canThrow && rng(5) == 0) { + } else if (canThrow && rng(20) == 0) { side_effects.splice(rng(side_effects.length + 1), 0, value); } else { side_effects.push(value); @@ -654,6 +667,7 @@ function filterDirective(s) { function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { var block_len = block_vars.length; + var class_len = classes.length; var nameLenBefore = VAR_NAMES.length; var consts = []; var lets = []; @@ -671,13 +685,22 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { fn(function() { consts.forEach(addAvoidVar); lets.forEach(addAvoidVar); + var s = []; + if (SUPPORT.class) while (rng(100) == 0) { + var name = "C" + clazz++; + classes.push(name); + s.push(createClassLiteral(recurmax,stmtDepth, canThrow, name)); + } if (rng(2)) { - return createDefinitions("const", consts) + "\n" + createDefinitions("let", lets) + "\n"; + s.push(createDefinitions("const", consts), createDefinitions("let", lets)); } else { - return createDefinitions("let", lets) + "\n" + createDefinitions("const", consts) + "\n"; + s.push(createDefinitions("let", lets), createDefinitions("const", consts)); } + s.push(""); + return s.join("\n"); }); VAR_NAMES.length = nameLenBefore; + classes.length = class_len; block_vars.length = block_len; function createDefinitions(type, names) { @@ -730,6 +753,7 @@ function mayCreateBlockVariables(recurmax, stmtDepth, canThrow, fn) { } function makeFunction(name) { + lambda_vars.push(name); if (generator) { name = "function* " + name; } else { @@ -757,6 +781,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var s = []; var name, args; var nameLenBefore = VAR_NAMES.length; + var lambda_len = lambda_vars.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; var save_generator = generator; @@ -801,6 +826,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var call_next = invokeGenerator(save_generator); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; if (!allowDefun) { @@ -920,7 +946,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn } else { init = "const "; } - if (!SUPPORT.destructuring || of && !(canThrow && rng(10) == 0) || rng(10)) { + if (!SUPPORT.destructuring || of && !(canThrow && rng(20) == 0) || rng(10)) { init += key; } else if (rng(5)) { init += "[ " + key + " ]"; @@ -932,7 +958,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn var await = SUPPORT.for_await_of && async && rng(20) == 0; if (SUPPORT.generator && rng(20) == 0) { var gen = getVarName(); - if (canThrow && rng(10) == 0) { + if (canThrow && rng(20) == 0) { s += gen + "; "; } else { s += gen + " && typeof " + gen + "[Symbol."; @@ -941,7 +967,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn } } else if (rng(5)) { s += createArrayLiteral(recurmax, stmtDepth, canThrow) + "; "; - } else if (canThrow && rng(10) == 0) { + } else if (canThrow && rng(20) == 0) { s += createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "; "; } else { s += '"" + (' + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "); "; @@ -1150,7 +1176,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { return [ "[ ", new Array(rng(3)).join(), - getVarName(NO_CONST), + getVarName(NO_CONST, NO_LAMBDA), new Array(rng(3)).join(), " ] = ", createArrayLiteral(recurmax, stmtDepth, canThrow), @@ -1159,13 +1185,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { return [ "{ ", rng(2) ? "" : createObjectKey(recurmax, stmtDepth, canThrow) + ": ", - getVarName(NO_CONST), + getVarName(NO_CONST, NO_LAMBDA), " } = ", createExpression(recurmax, COMMA_OK, stmtDepth, canThrow), " || {}", ].join(""); 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++: return createExpression(recurmax, COMMA_OK, stmtDepth, canThrow); @@ -1174,6 +1200,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: case p++: var nameLenBefore = VAR_NAMES.length; + var lambda_len = lambda_vars.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; var save_generator = generator; @@ -1185,9 +1212,9 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { 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. - unique_vars.pop(); + unique_vars.length -= 3; var s = []; switch (rng(5)) { case 0: @@ -1218,8 +1245,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { s.push("(" + params); switch (rng(10)) { case 0: - s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); - break; + if (!in_class) { + s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); + break; + } case 1: s.push("(this && this." + getDotKey() + ")"); break; @@ -1232,6 +1261,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { }); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow); if (args) suffix += args; @@ -1275,24 +1305,25 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var instantiate = rng(4) ? "new " : ""; createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { s.push( - instantiate + "function " + name + "(" + createParams(save_async, save_generator) + "){", + instantiate + makeFunction(name) + "(" + createParams(save_async, save_generator) + "){", strictMode(), defns() ); if (instantiate) for (var i = rng(4); --i >= 0;) { - if (rng(2)) s.push("this." + getDotKey(true) + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); - else s.push("this[" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "]" + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ";"); + s.push((in_class ? "if (this) " : "") + createThisAssignment(recurmax, stmtDepth, canThrow)); } s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); }); generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; s.push(rng(2) ? "}" : "}" + createArgs(recurmax, stmtDepth, canThrow, instantiate)); break; } generator = save_generator; async = save_async; + lambda_vars.length = lambda_len; VAR_NAMES.length = nameLenBefore; return filterDirective(s).join("\n"); case p++: @@ -1358,18 +1389,40 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { case p++: var name = getVarName(); 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++: var name = getVarName(); var s = name + "." + getDotKey(); - return canThrow && rng(8) == 0 ? s : name + " && " + s; + return canThrow && rng(20) == 0 ? s : name + " && " + s; case p++: case p++: var name = getVarName(); var s = name + "." + getDotKey(); 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++: + 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++: @@ -1391,7 +1444,7 @@ function createArrayLiteral(recurmax, stmtDepth, canThrow) { case 0: case 1: var name = getVarName(); - if (canThrow && rng(8) === 0) { + if (canThrow && rng(20) == 0) { arr.push("..." + name); } else { arr.push('...("" + ' + name + ")"); @@ -1475,14 +1528,33 @@ function createObjectKey(recurmax, stmtDepth, canThrow) { 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 save_async = async; var save_generator = generator; 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; - switch (rng(SUPPORT.computed_key ? 3 : 2)) { + switch (internal ? 2 : rng(SUPPORT.computed_key ? 3 : 2)) { case 0: async = false; generator = false; @@ -1493,7 +1565,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { defns(), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), createStatement(recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth, STMT_RETURN_ETC), - "},", + "}", ]; }; break; @@ -1511,18 +1583,24 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { defns(), _createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), "this." + prop + createAssignment() + _createBinaryExpr(recurmax, COMMA_OK, stmtDepth, canThrow) + ";", - "},", + "}", ]; }; break; default: - async = SUPPORT.async && rng(50) == 0; - generator = SUPPORT.generator && rng(50) == 0; - if (async && generator && !SUPPORT.async_generator) { - if (rng(2)) { - async = false; - } else { - generator = false; + if (/^(constructor|super)$/.test(internal)) { + async = false; + generator = false; + name = "constructor"; + } else { + async = SUPPORT.async && rng(50) == 0; + generator = SUPPORT.generator && rng(50) == 0; + if (async && generator && !SUPPORT.async_generator) { + if (rng(2)) { + async = false; + } else { + generator = false; + } } } fn = function(defns) { @@ -1532,9 +1610,13 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { name + "(" + createParams(save_async, save_generator, NO_DUPLICATE) + "){", strictMode(), 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; } @@ -1561,7 +1643,7 @@ function createObjectLiteral(recurmax, stmtDepth, canThrow) { obj.push(getVarName() + ","); break; case 4: - obj.push(createObjectFunction(recurmax, stmtDepth, canThrow)); + obj.push(createObjectFunction(recurmax, stmtDepth, canThrow) + ","); break; default: 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"); } +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) { recurmax = 3; // note that this generates 2^recurmax expression parts... make sure to cap it return _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow); @@ -1589,7 +1728,7 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { case 1: return "(" + createUnarySafePrefix() + "(" + _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + "))"; case 2: - assignee = getVarName(NO_CONST); + assignee = getVarName(NO_CONST, NO_LAMBDA); return "(" + assignee + createAssignment() + _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow) + ")"; case 3: assignee = getVarName(); @@ -1627,7 +1766,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { ].join(""); 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: assignee = getVarName(); switch (SUPPORT.destructuring ? rng(20) : 2) { @@ -1664,7 +1804,8 @@ function _createSimpleBinaryExpr(recurmax, noComma, stmtDepth, canThrow) { ].join(""); 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: return _createBinaryExpr(recurmax, noComma, stmtDepth, canThrow); } @@ -1715,11 +1856,19 @@ function createAssignment() { } 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() { - 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() { @@ -1735,7 +1884,18 @@ function removeAvoidVar(name) { 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` var name, tries = 10; do { @@ -1743,9 +1903,14 @@ function getVarName(noConst) { name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)]; } while (!name || avoid_vars.indexOf(name) >= 0 - || noConst && block_vars.indexOf(name) >= 0 - || async && name == "await" - || generator && name == "yield"); + || noConst && (block_vars.indexOf(name) >= 0 + || in_class && [ + "Infinity", + "NaN", + "undefined", + ].indexOf(name) >= 0) + || noLambda && lambda_vars.indexOf(name) >= 0 + || isBannedKeyword(name)); return name; } @@ -1759,8 +1924,7 @@ function createVarName(maybe, dontStore) { if (suffix) name += "_" + suffix; } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 - || async && name == "await" - || generator && name == "yield"); + || isBannedKeyword(name)); if (!dontStore) VAR_NAMES.push(name); return name; } @@ -2006,6 +2170,16 @@ function is_error_destructuring(ex) { 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) { var stack = [ { code: orig, @@ -2067,6 +2241,12 @@ function patch_try_catch(orig, toplevel) { } else if (is_error_destructuring(result)) { index = result.ufuzz_catch; return orig.slice(0, index) + result.ufuzz_var + ' = new Error("cannot destructure");' + orig.slice(index); + } 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; @@ -2168,6 +2348,16 @@ for (var round = 1; round <= num_iterations; round++) { } // ignore difference in error message caused by Temporal Dead Zone if (!ok && errored && uglify_result.name == "ReferenceError" && original_result.name == "ReferenceError") ok = true; + // ignore 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` if (!ok && errored && is_error_in(uglify_result) && is_error_in(original_result)) ok = true; // 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)) { 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 if (!ok) { var orig_skipped = patch_try_catch(original_code, toplevel); diff --git a/tools/exports.js b/tools/exports.js index 64a46e30..1d2d510e 100644 --- a/tools/exports.js +++ b/tools/exports.js @@ -1,4 +1,5 @@ exports["Dictionary"] = Dictionary; +exports["is_statement"] = is_statement; exports["List"] = List; exports["minify"] = minify; exports["parse"] = parse; |