Skip to content

Commit

Permalink
Adds the simpleAutoAccessors parser plugin
Browse files Browse the repository at this point in the history
This is the first part of the implementation of the latest version of
the decorators proposal: https://github.com/tc39/proposal-decorators

The keyword is outlined here in the README: https://github.com/tc39/proposal-decorators#class-auto-accessors

It adds a `simpleAutoAccessors` plugin which can be used to parse the
`accessor` keyword into ClassAccessorProperty and ClassPrivateAccessorProperty
node types.
  • Loading branch information
pzuraq committed Sep 19, 2021
1 parent 3d1481f commit 5f6f23e
Show file tree
Hide file tree
Showing 47 changed files with 1,196 additions and 5 deletions.
65 changes: 65 additions & 0 deletions packages/babel-generator/src/generators/classes.ts
Expand Up @@ -114,6 +114,48 @@ export function ClassProperty(this: Printer, node: t.ClassProperty) {
this.semicolon();
}

export function ClassAccessorProperty(
this: Printer,
node: t.ClassAccessorProperty,
) {
this.printJoin(node.decorators, node);

// catch up to property key, avoid line break
// between member modifiers and the property key.
this.source("end", node.key.loc);

this.tsPrintClassMemberModifiers(node, /* isField */ true);

this.word("accessor");
this.space();

if (node.computed) {
this.token("[");
this.print(node.key, node);
this.token("]");
} else {
this._variance(node);
this.print(node.key, node);
}

// TS
if (node.optional) {
this.token("?");
}
if (node.definite) {
this.token("!");
}

this.print(node.typeAnnotation, node);
if (node.value) {
this.space();
this.token("=");
this.space();
this.print(node.value, node);
}
this.semicolon();
}

export function ClassPrivateProperty(
this: Printer,
node: t.ClassPrivateProperty,
Expand All @@ -134,6 +176,29 @@ export function ClassPrivateProperty(
this.semicolon();
}

export function ClassPrivateAccessorProperty(
this: Printer,
node: t.ClassPrivateAccessorProperty,
) {
this.printJoin(node.decorators, node);
if (node.static) {
this.word("static");
this.space();
}
this.word("accessor");
this.space();

this.print(node.key, node);
this.print(node.typeAnnotation, node);
if (node.value) {
this.space();
this.token("=");
this.space();
this.print(node.value, node);
}
this.semicolon();
}

export function ClassMethod(this: Printer, node: t.ClassMethod) {
this._classMethodHead(node);
this.space();
Expand Down
23 changes: 23 additions & 0 deletions packages/babel-parser/ast/spec.md
Expand Up @@ -1213,6 +1213,29 @@ interface ClassPrivateProperty <: Node {
}
```

## ClassAccessorProperty

```js
interface ClassAccessorProperty <: Node {
type: "ClassAccessorProperty";
key: Expression;
value: Expression;
static: boolean;
computed: boolean;
}
```

## ClassPrivateAccessorProperty

```js
interface ClassPrivateAccessorProperty <: Node {
type: "ClassPrivateAccessorProperty";
key: PrivateName;
value: Expression;
static: boolean;
}
```

## StaticBlock

```js
Expand Down
71 changes: 69 additions & 2 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1393,6 +1393,7 @@ export default class StatementParser extends ExpressionParser {
prop.computed = false;
prop.key = key;
prop.static = false;
classBody.body.push(this.parseClassProperty(prop));
return true;
}
Expand Down Expand Up @@ -1429,8 +1430,11 @@ export default class StatementParser extends ExpressionParser {
) {
const publicMethod: $FlowSubtype<N.ClassMethod> = member;
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
const publicProp: $FlowSubtype<N.ClassMethod> = member;
const privateProp: $FlowSubtype<N.ClassPrivateMethod> = member;
const publicProp: $FlowSubtype<N.ClassProperty> = member;
const privateProp: $FlowSubtype<N.ClassPrivateProperty> = member;
const publicAccessorProp: $FlowSubtype<N.ClassAccessorProperty> = member;
const privateAccessorProp: $FlowSubtype<N.ClassPrivateAccessorProperty> =
member;
const method: typeof publicMethod | typeof privateMethod = publicMethod;
const publicMember: typeof publicMethod | typeof publicProp = publicMethod;
Expand Down Expand Up @@ -1583,6 +1587,23 @@ export default class StatementParser extends ExpressionParser {
}

this.checkGetterSetterParams(publicMethod);
} else if (
isContextual &&
key.name === "accessor" &&
this.hasPlugin("simpleAutoAccessors") &&
!this.isLineTerminator()
) {
this.resetPreviousNodeTrailingComments(key);

// The so-called parsed name would have been "accessor": get the real name.
const isPrivate = this.match(tt.privateName);
this.parseClassElementName(publicProp);

if (isPrivate) {
this.pushClassPrivateAccessorProperty(classBody, privateAccessorProp);
} else {
this.pushClassAccessorProperty(classBody, publicAccessorProp);
}
} else if (this.isLineTerminator()) {
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
if (isPrivate) {
Expand Down Expand Up @@ -1664,6 +1685,36 @@ export default class StatementParser extends ExpressionParser {
);
}

pushClassAccessorProperty(
classBody: N.ClassBody,
prop: N.ClassAccessorProperty,
) {
if (
!prop.computed &&
(prop.key.name === "constructor" || prop.key.value === "constructor")
) {
// Non-computed field, which is either an identifier named "constructor"
// or a string literal named "constructor"
this.raise(prop.key.start, Errors.ConstructorClassField);
}

classBody.body.push(this.parseClassAccessorProperty(prop));
}

pushClassPrivateAccessorProperty(
classBody: N.ClassBody,
prop: N.ClassPrivateAccessorProperty,
) {
const node = this.parseClassPrivateAccessorProperty(prop);
classBody.body.push(node);

this.classScope.declarePrivateName(
this.getPrivateNameSV(node.key),
CLASS_ELEMENT_OTHER,
node.key.start,
);
}

pushClassMethod(
classBody: N.ClassBody,
method: N.ClassMethod,
Expand Down Expand Up @@ -1741,6 +1792,22 @@ export default class StatementParser extends ExpressionParser {
return this.finishNode(node, "ClassProperty");
}

parseClassPrivateAccessorProperty(
node: N.ClassPrivateAccessorProperty,
): N.ClassPrivateAccessorProperty {
this.parseInitializer(node);
this.semicolon();
return this.finishNode(node, "ClassPrivateAccessorProperty");
}

parseClassAccessorProperty(
node: N.ClassAccessorProperty,
): N.ClassAccessorProperty {
this.parseInitializer(node);
this.semicolon();
return this.finishNode(node, "ClassAccessorProperty");
}

// https://tc39.es/proposal-class-fields/#prod-Initializer
parseInitializer(node: N.ClassProperty | N.ClassPrivateProperty): void {
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
Expand Down
31 changes: 31 additions & 0 deletions packages/babel-parser/src/types.js
Expand Up @@ -849,6 +849,37 @@ export type ClassPrivateProperty = NodeBase & {
override?: true,
};

export type ClassAccessorProperty = ClassMemberBase &
DeclarationBase & {
type: "ClassAccesorProperty",
key: Expression,
init?: Expression, // TODO: Not in spec that this is nullable.

typeAnnotation?: ?TypeAnnotationBase, // TODO: Not in spec
variance?: ?FlowVariance, // TODO: Not in spec

// TypeScript only: (TODO: Not in spec)
readonly?: true,
definite?: true,
};

export type ClassPrivateAccessorProperty = NodeBase & {
type: "ClassPrivateAccessorProperty",
key: PrivateName,
init?: Expression, // TODO: Not in spec that this is nullable.
static: boolean,
computed: false,

// Flow and Typescript
typeAnnotation?: ?TypeAnnotationBase,

// TypeScript only
optional?: true,
definite?: true,
readonly?: true,
override?: true,
};

export type OptClassDeclaration = ClassBase &
DeclarationBase &
HasDecorators & {
Expand Down
@@ -0,0 +1,3 @@
class Foo {
accessor constructor;
}
@@ -0,0 +1,44 @@
{
"type": "File",
"start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"errors": [
"SyntaxError: Classes may not have a field named 'constructor'. (2:11)"
],
"program": {
"type": "Program",
"start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start":0,"end":37,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"id": {
"type": "Identifier",
"start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"},
"name": "Foo"
},
"superClass": null,
"body": {
"type": "ClassBody",
"start":10,"end":37,"loc":{"start":{"line":1,"column":10},"end":{"line":3,"column":1}},
"body": [
{
"type": "ClassAccessorProperty",
"start":14,"end":35,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":23}},
"static": false,
"key": {
"type": "Identifier",
"start":23,"end":34,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":22},"identifierName":"constructor"},
"name": "constructor"
},
"computed": false,
"value": null
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,4 @@
class Foo {
accessor
bar;
}
@@ -0,0 +1,53 @@
{
"type": "File",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"program": {
"type": "Program",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start":0,"end":31,"loc":{"start":{"line":1,"column":0},"end":{"line":4,"column":1}},
"id": {
"type": "Identifier",
"start":6,"end":9,"loc":{"start":{"line":1,"column":6},"end":{"line":1,"column":9},"identifierName":"Foo"},
"name": "Foo"
},
"superClass": null,
"body": {
"type": "ClassBody",
"start":10,"end":31,"loc":{"start":{"line":1,"column":10},"end":{"line":4,"column":1}},
"body": [
{
"type": "ClassProperty",
"start":14,"end":22,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":10}},
"static": false,
"key": {
"type": "Identifier",
"start":14,"end":22,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":10},"identifierName":"accessor"},
"name": "accessor"
},
"computed": false,
"value": null
},
{
"type": "ClassProperty",
"start":25,"end":29,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":6}},
"static": false,
"key": {
"type": "Identifier",
"start":25,"end":28,"loc":{"start":{"line":3,"column":2},"end":{"line":3,"column":5},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"value": null
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
class Foo {
accessor bar;
}

0 comments on commit 5f6f23e

Please sign in to comment.