diff --git a/lib/ast.js b/lib/ast.js index 8dadf21c..a80ea76c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -955,6 +955,14 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { $documentation: "An object getter property", }, AST_ObjectProperty); +var AST_Class = DEFNODE("Class", "name extends", { + $propdoc: { + name: "[AST_SymbolClassName?] optional class name.", + extends: "[AST_Node]? optional parent class", + }, + $documentation: "An ES6 class", +}, AST_Object); + var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $propdoc: { name: "[string] name of this symbol", @@ -999,6 +1007,10 @@ var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { $documentation: "Symbol naming a function expression", }, AST_SymbolDeclaration); +var AST_SymbolClassName = DEFNODE("SymbolClassName", null, { + $documentation: "Symbol naming a class's name. Lexically scoped to the class." +}, AST_SymbolDeclaration); + var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { $documentation: "Symbol naming the exception in catch", }, AST_SymbolDeclaration); diff --git a/lib/output.js b/lib/output.js index bdab47c6..e597184f 100644 --- a/lib/output.js +++ b/lib/output.js @@ -502,6 +502,12 @@ function OutputStream(options) { return first_in_statement(output); }); + // Not a class, though. It can be alone in a statement although + // it extends from AST_Object. + PARENS(AST_Class, function() { + return false; + }); + PARENS([ AST_Unary, AST_Undefined ], function(output){ var p = output.parent(); return p instanceof AST_PropAccess && p.expression === this; @@ -1188,6 +1194,31 @@ function OutputStream(options) { }); else output.print("{}"); }); + DEFPRINT(AST_Class, function(self, output){ + output.print("class"); + output.space(); + if (self.name) { + self.name.print(output); + output.space(); + } + if (self.extends) { + output.print("extends"); + output.space(); + self.extends.print(output); + output.space(); + } + if (self.properties.length > 0) output.with_block(function(){ + self.properties.forEach(function(prop, i){ + if (i) { + output.newline(); + } + output.indent(); + prop.print(output); + }); + output.newline(); + }); + else output.print("{}"); + }); DEFPRINT(AST_ObjectKeyVal, function(self, output){ var key = self.key; var quote = self.quote; diff --git a/lib/parse.js b/lib/parse.js index 4ed32863..9af773d5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,9 +44,9 @@ "use strict"; -var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var let void while with'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with'; var KEYWORDS_ATOM = 'false null true'; -var RESERVED_WORDS = 'abstract 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' +var RESERVED_WORDS = 'abstract 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; var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case'; @@ -855,6 +855,9 @@ function parse($TEXT, options) { case "for": return for_(); + case "class": + return class_(); + case "function": return function_(AST_Defun); @@ -1374,6 +1377,13 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } + if (is("keyword", "class")) { + next(); + var cls = class_(); + cls.start = start; + cls.end = prev(); + return subscripts(cls, allow_calls); + } if (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } @@ -1446,32 +1456,9 @@ function parse($TEXT, options) { var type = start.type; var name = as_property_name(); if (type != "string" && type != "num" && !is("punc", ":")) { - if (is("punc", "(")) { - a.push(new AST_ConciseMethod({ - start : start, - name : new AST_SymbolMethod({ name: name }), - argnames : params_or_seq_().as_params(croak), - body : _function_body(true), - end : prev() - })) - continue; - } - if (name == "get") { - a.push(new AST_ObjectGetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); - continue; - } - if (name == "set") { - a.push(new AST_ObjectSetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); + var concise = concise_method_or_getset(name, start); + if (concise) { + a.push(concise); continue; } } @@ -1510,6 +1497,69 @@ function parse($TEXT, options) { return new AST_Object({ properties: a }) }); + function class_() { + var start, method, class_name, name, extends_, a = []; + + if (S.token.type == "name" && S.token.value != "extends") { + class_name = as_symbol(AST_SymbolClassName) + } + + if (S.token.value == "extends") { + next(); + extends_ = expression(true); + } + + expect("{"); + + if (is("punc", ";")) { next(); } // Leading semicolons are okay in class bodies. + while (!is("punc", "}")) { + start = S.token; + name = as_property_name(); + method = concise_method_or_getset(name, start); + if (!method) { croak(); } + a.push(method); + if (is("punc", ";")) { next(); } + } + + next(); + + return new AST_Class({ + start: start, + name: class_name, + extends: extends_, + properties: a, + end: prev(), + }); + } + + function concise_method_or_getset(name, start) { + if (is("punc", "(")) { + return new AST_ConciseMethod({ + start : start, + name : new AST_SymbolMethod({ name: name }), + argnames : params_or_seq_().as_params(croak), + body : _function_body(true), + end : prev() + }); + } + if (name == "get") { + return new AST_ObjectGetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + }); + } + if (name == "set") { + return new AST_ObjectSetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + }); + } + } + function as_property_name() { var tmp = S.token; next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 731976f3..95e9bd54 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -181,6 +181,25 @@ concise_methods_and_keyword_names: { } } +classes: { + input: { + class SomeClass { + constructor() { + }; + foo() {}; + }; + class NoSemi { + constructor(...args) { + } + foo() {} + }; + class ChildClass extends SomeClass {}; + var asExpression = class AsExpression {}; + var nameless = class {}; + } + expect_exact: "class SomeClass{constructor(){}foo(){}}class NoSemi{constructor(...args){}foo(){}}class ChildClass extends SomeClass{}var asExpression=class AsExpression{};var nameless=class{};" +} + number_literals: { input: { 0b1001;