support class literals (#4658)

This commit is contained in:
Alex Lam S.L
2021-02-23 14:55:08 +00:00
committed by GitHub
parent e535f19189
commit d68d155f93
18 changed files with 1701 additions and 175 deletions

View File

@@ -217,6 +217,12 @@ var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)"
}, 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);
}
},