diff options
author | Alex Lam S.L <alexlamsl@gmail.com> | 2021-02-23 14:55:08 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-23 22:55:08 +0800 |
commit | d68d155f93a355a9f6f0451150186b7fad8c58b8 (patch) | |
tree | e3cba7df7cd9d2221ff2b97ea30d8c9a07e7bba1 /lib | |
parent | e535f1918915251681df6ffe80a56d56672685ea (diff) | |
download | tracifyjs-d68d155f93a355a9f6f0451150186b7fad8c58b8.tar.gz tracifyjs-d68d155f93a355a9f6f0451150186b7fad8c58b8.zip |
support `class` literals (#4658)
Diffstat (limited to 'lib')
-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 |
6 files changed, 654 insertions, 107 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); |