Skip to content

Commit

Permalink
Parse class static block (#12079)
Browse files Browse the repository at this point in the history
Co-authored-by: Brian Ng <bng412@gmail.com>
  • Loading branch information
2 people authored and nicolo-ribaudo committed Oct 14, 2020
1 parent 6830c90 commit 3ccca88
Show file tree
Hide file tree
Showing 66 changed files with 1,823 additions and 46 deletions.
15 changes: 15 additions & 0 deletions packages/babel-generator/src/generators/classes.js
Expand Up @@ -134,3 +134,18 @@ export function _classMethodHead(node) {
this.tsPrintClassMemberModifiers(node, /* isField */ false);
this._methodHead(node);
}

export function StaticBlock(node) {
this.word("static");
this.space();
this.token("{");
if (node.body.length === 0) {
this.token("}");
} else {
this.newline();
this.printSequence(node.body, node, {
indent: true,
});
this.rightBrace();
}
}
@@ -0,0 +1,16 @@
class Foo {
static {}
}

class A1 {
static{
foo;
}
}

class A2 {
static {
foo;
bar;
}
}
@@ -0,0 +1,4 @@
{
"minified": true,
"plugins": ["classStaticBlock"]
}
@@ -0,0 +1 @@
class Foo{static{}}class A1{static{foo}}class A2{static{foo;bar}}
@@ -0,0 +1,15 @@
class Foo {
static {}
}

class A1 {
static{
foo;
}
}

class A2 {
static {
foo;bar;
}
}
@@ -0,0 +1 @@
{ "plugins": ["classStaticBlock"] }
@@ -0,0 +1,16 @@
class Foo {
static {}
}

class A1 {
static {
foo;
}
}

class A2 {
static {
foo;
bar;
}
}
13 changes: 12 additions & 1 deletion packages/babel-parser/ast/spec.md
Expand Up @@ -1183,7 +1183,7 @@ interface Class <: Node {
```js
interface ClassBody <: Node {
type: "ClassBody";
body: [ ClassMethod | ClassPrivateMethod | ClassProperty | ClassPrivateProperty ];
body: [ ClassMethod | ClassPrivateMethod | ClassProperty | ClassPrivateProperty | StaticBlock ];
}
```

Expand Down Expand Up @@ -1235,6 +1235,17 @@ interface ClassPrivateProperty <: Node {
}
```

## StaticBlock

```js
interface StaticBlock <: Node {
type: "StaticBlock";
body: [ Statement ];
}
```

A static block proposed in https://github.com/tc39/proposal-class-static-block.

## ClassDeclaration

```js
Expand Down
6 changes: 4 additions & 2 deletions packages/babel-parser/src/parser/error-message.js
Expand Up @@ -4,8 +4,8 @@
// The Errors key follows https://cs.chromium.org/chromium/src/v8/src/common/message-template.h unless it does not exist
export const ErrorMessages = Object.freeze({
AccessorIsGenerator: "A %0ter cannot be a generator",
ArgumentsDisallowedInInitializer:
"'arguments' is not allowed in class field initializer",
ArgumentsInClass:
"'arguments' is only allowed in functions and class methods",
AsyncFunctionInSingleStatementContext:
"Async functions can only be declared at the top level or inside a block",
AwaitBindingIdentifier:
Expand All @@ -32,6 +32,7 @@ export const ErrorMessages = Object.freeze({
DecoratorExportClass:
"Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.",
DecoratorSemicolon: "Decorators must not be followed by a semicolon",
DecoratorStaticBlock: "Decorators can't be used with a static block",
DeletePrivateField: "Deleting a private field is not allowed",
DestructureNamedImport:
"ES2015 named imports do not destructure. Use another statement for destructuring after the import.",
Expand All @@ -41,6 +42,7 @@ export const ErrorMessages = Object.freeze({
"`%0` has already been exported. Exported identifiers must be unique.",
DuplicateProto: "Redefinition of __proto__ property",
DuplicateRegExpFlags: "Duplicate regular expression flag",
DuplicateStaticBlock: "Duplicate static block in the same class",
ElementAfterRest: "Rest element must be last element",
EscapedCharNotAnIdentifier: "Invalid Unicode escape",
ExportBindingIsString:
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-parser/src/parser/expression.js
Expand Up @@ -2390,7 +2390,7 @@ export default class ExpressionParser extends LValParser {
!this.scope.inNonArrowFunction &&
word === "arguments"
) {
this.raise(startLoc, Errors.ArgumentsDisallowedInInitializer);
this.raise(startLoc, Errors.ArgumentsInClass);
return;
}
if (checkKeywords && isKeyword(word)) {
Expand Down
71 changes: 53 additions & 18 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -797,7 +797,7 @@ export default class StatementParser extends ExpressionParser {
}

// Parse a semicolon-enclosed block of statements, handling `"use
// strict"` declarations when `allowStrict` is true (used for
// strict"` declarations when `allowDirectives` is true (used for
// function bodies).

parseBlock(
Expand Down Expand Up @@ -1207,7 +1207,11 @@ export default class StatementParser extends ExpressionParser {
): N.ClassBody {
this.classScope.enter();
const state = { hadConstructor: false };
const state: N.ParseClassMemberState = {
constructorAllowsSuper,
hadConstructor: false,
hadStaticBlock: false,
};
let decorators: N.Decorator[] = [];
const classBody: N.ClassBody = this.startNode();
classBody.body = [];
Expand Down Expand Up @@ -1239,7 +1243,7 @@ export default class StatementParser extends ExpressionParser {
decorators = [];
}
this.parseClassMember(classBody, member, state, constructorAllowsSuper);
this.parseClassMember(classBody, member, state);
if (
member.kind === "constructor" &&
Expand Down Expand Up @@ -1305,31 +1309,33 @@ export default class StatementParser extends ExpressionParser {
parseClassMember(
classBody: N.ClassBody,
member: N.ClassMember,
state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
state: N.ParseClassMemberState,
): void {
const isStatic = this.isContextual("static");
if (isStatic && this.parseClassMemberFromModifier(classBody, member)) {
// a class element named 'static'
return;
if (isStatic) {
if (this.parseClassMemberFromModifier(classBody, member)) {
// a class element named 'static'
return;
}
if (this.eat(tt.braceL)) {
this.parseClassStaticBlock(
classBody,
((member: any): N.StaticBlock),
state,
);
return;
}
}
this.parseClassMemberWithIsStatic(
classBody,
member,
state,
isStatic,
constructorAllowsSuper,
);
this.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
}
parseClassMemberWithIsStatic(
classBody: N.ClassBody,
member: N.ClassMember,
state: { hadConstructor: boolean },
state: N.ParseClassMemberState,
isStatic: boolean,
constructorAllowsSuper: boolean,
) {
const publicMethod: $FlowSubtype<N.ClassMethod> = member;
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
Expand Down Expand Up @@ -1396,7 +1402,7 @@ export default class StatementParser extends ExpressionParser {
this.raise(key.start, Errors.DuplicateConstructor);
}
state.hadConstructor = true;
allowsDirectSuper = constructorAllowsSuper;
allowsDirectSuper = state.constructorAllowsSuper;
}

this.pushClassMethod(
Expand Down Expand Up @@ -1515,6 +1521,35 @@ export default class StatementParser extends ExpressionParser {
return key;
}
parseClassStaticBlock(
classBody: N.ClassBody,
member: N.StaticBlock & { decorators?: Array<N.Decorator> },
state: N.ParseClassMemberState,
) {
this.expectPlugin("classStaticBlock", member.start);
// Start a new lexical scope
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
// Start a new scope with regard to loop labels
const oldLabels = this.state.labels;
this.state.labels = [];
// ClassStaticBlockStatementList:
// StatementList[~Yield, ~Await, ~Return] opt
this.prodParam.enter(PARAM);
const body = (member.body = []);
this.parseBlockOrModuleBlockBody(body, undefined, false, tt.braceR);
this.prodParam.exit();
this.scope.exit();
this.state.labels = oldLabels;
classBody.body.push(this.finishNode<N.StaticBlock>(member, "StaticBlock"));
if (state.hadStaticBlock) {
this.raise(member.start, Errors.DuplicateStaticBlock);
}
if (member.decorators?.length) {
this.raise(member.start, Errors.DecoratorStaticBlock);
}
state.hadStaticBlock = true;
}
pushClassProperty(classBody: N.ClassBody, prop: N.ClassProperty) {
if (
!prop.computed &&
Expand Down
5 changes: 2 additions & 3 deletions packages/babel-parser/src/plugins/flow.js
Expand Up @@ -2096,8 +2096,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassMember(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
state: N.ParseClassMemberState,
): void {
const pos = this.state.start;
if (this.isContextual("declare")) {
Expand All @@ -2109,7 +2108,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
member.declare = true;
}

super.parseClassMember(classBody, member, state, constructorAllowsSuper);
super.parseClassMember(classBody, member, state);

if (member.declare) {
if (
Expand Down
21 changes: 4 additions & 17 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -2102,21 +2102,15 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassMember(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
state: N.ParseClassMemberState,
): void {
this.tsParseModifiers(member, ["declare"]);
const accessibility = this.parseAccessModifier();
if (accessibility) member.accessibility = accessibility;
this.tsParseModifiers(member, ["declare"]);

const callParseClassMember = () => {
super.parseClassMember(
classBody,
member,
state,
constructorAllowsSuper,
);
super.parseClassMember(classBody, member, state);
};
if (member.declare) {
this.tsInDeclareContext(callParseClassMember);
Expand All @@ -2128,9 +2122,8 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassMemberWithIsStatic(
classBody: N.ClassBody,
member: N.ClassMember | N.TsIndexSignature,
state: { hadConstructor: boolean },
state: N.ParseClassMemberState,
isStatic: boolean,
constructorAllowsSuper: boolean,
): void {
this.tsParseModifiers(member, ["abstract", "readonly", "declare"]);

Expand Down Expand Up @@ -2160,13 +2153,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>

/*:: invariant(member.type !== "TSIndexSignature") */

super.parseClassMemberWithIsStatic(
classBody,
member,
state,
isStatic,
constructorAllowsSuper,
);
super.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
}

parsePostMemberNameModifiers(
Expand Down
13 changes: 12 additions & 1 deletion packages/babel-parser/src/types.js
Expand Up @@ -705,7 +705,7 @@ export type ClassBase = HasDecorators & {

export type ClassBody = NodeBase & {
type: "ClassBody",
body: Array<ClassMember | TsIndexSignature>, // TODO: $ReadOnlyArray
body: Array<ClassMember | StaticBlock | TsIndexSignature>, // TODO: $ReadOnlyArray
};
// | Placeholder<"ClassBody">;

Expand All @@ -719,6 +719,11 @@ export type ClassMemberBase = NodeBase &
optional?: ?true,
};

export type StaticBlock = NodeBase & {
type: "StaticBlock",
body: Array<Statement>,
};

export type ClassMember =
| ClassMethod
| ClassPrivateMethod
Expand Down Expand Up @@ -1489,3 +1494,9 @@ export type ParseSubscriptState = {
maybeAsyncArrow: boolean,
stop: boolean,
};

export type ParseClassMemberState = {|
hadConstructor: boolean,
hadStaticBlock: boolean,
constructorAllowsSuper: boolean,
|};
@@ -0,0 +1,6 @@
class C {
static foo() {}
static {
this.bar = this.foo;
}
}
@@ -0,0 +1,3 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: 'classStaticBlock' (3:2)"
}
Expand Up @@ -2,7 +2,7 @@
"type": "File",
"start":0,"end":60,"loc":{"start":{"line":1,"column":0},"end":{"line":5,"column":1}},
"errors": [
"SyntaxError: 'arguments' is not allowed in class field initializer (3:16)"
"SyntaxError: 'arguments' is only allowed in functions and class methods (3:16)"
],
"program": {
"type": "Program",
Expand Down
Expand Up @@ -2,7 +2,7 @@
"type": "File",
"start":0,"end":76,"loc":{"start":{"line":1,"column":0},"end":{"line":5,"column":1}},
"errors": [
"SyntaxError: 'arguments' is not allowed in class field initializer (3:25)"
"SyntaxError: 'arguments' is only allowed in functions and class methods (3:25)"
],
"program": {
"type": "Program",
Expand Down

0 comments on commit 3ccca88

Please sign in to comment.