Skip to content

Commit

Permalink
Add the decoratorsAutoAccessors parser plugin (#13681)
Browse files Browse the repository at this point in the history
Co-authored-by: Huáng Jùnliàng <jlhwung@gmail.com>
Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
  • Loading branch information
3 people committed Dec 16, 2021
1 parent 7178fbb commit ee06a98
Show file tree
Hide file tree
Showing 50 changed files with 1,095 additions and 5 deletions.
43 changes: 43 additions & 0 deletions packages/babel-generator/src/generators/classes.ts
Expand Up @@ -115,6 +115,49 @@ 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.printInnerComments(node);
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 Down
12 changes: 12 additions & 0 deletions packages/babel-parser/ast/spec.md
Expand Up @@ -1213,6 +1213,18 @@ interface ClassPrivateProperty <: Node {
}
```

## ClassAccessorProperty

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

## StaticBlock

```js
Expand Down
57 changes: 54 additions & 3 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1518,8 +1518,9 @@ 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 accessorProp: $FlowSubtype<N.ClassAccessorProperty> = member;
const method: typeof publicMethod | typeof privateMethod = publicMethod;
const publicMember: typeof publicMethod | typeof publicProp = publicMethod;
Expand Down Expand Up @@ -1674,6 +1675,18 @@ export default class StatementParser extends ExpressionParser {
}

this.checkGetterSetterParams(publicMethod);
} else if (
isContextual &&
key.name === "accessor" &&
!this.isLineTerminator()
) {
this.expectPlugin("decoratorAutoAccessors");
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);
this.pushClassAccessorProperty(classBody, accessorProp, isPrivate);
} 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 @@ -1759,6 +1772,34 @@ export default class StatementParser extends ExpressionParser {
);
}

pushClassAccessorProperty(
classBody: N.ClassBody,
prop: N.ClassAccessorProperty,
isPrivate: boolean,
) {
if (!isPrivate && !prop.computed) {
// Not private, so not node is not a PrivateName and we can safely cast
const key = (prop.key: N.Expression);

if (key.name === "constructor" || 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);
}
}

const node = this.parseClassAccessorProperty(prop);
classBody.body.push(node);

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

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

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

// https://tc39.es/ecma262/#prod-Initializer
parseInitializer(node: N.ClassProperty | N.ClassPrivateProperty): void {
parseInitializer(
node: N.ClassProperty | N.ClassPrivateProperty | N.ClassAccessorProperty,
): void {
this.scope.enter(SCOPE_CLASS | SCOPE_SUPER);
this.expressionScope.enter(newExpressionScope());
this.prodParam.enter(PARAM);
Expand Down
17 changes: 16 additions & 1 deletion packages/babel-parser/src/types.js
Expand Up @@ -782,7 +782,8 @@ export type ClassMember =
| ClassMethod
| ClassPrivateMethod
| ClassProperty
| ClassPrivateProperty;
| ClassPrivateProperty
| ClassAccessorProperty;
export type MethodLike =
| ObjectMethod
Expand Down Expand Up @@ -855,6 +856,20 @@ export type ClassPrivateProperty = NodeBase & {
variance?: ?FlowVariance,
};

export type ClassAccessorProperty = ClassMemberBase &
DeclarationBase & {
type: "ClassAccessorProperty",
key: Expression | PrivateName,
value: ?Expression,

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

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

export type OptClassDeclaration = ClassBase &
DeclarationBase &
HasDecorators & {
Expand Down
@@ -0,0 +1,3 @@
class Foo {
accessor bar;
}
@@ -0,0 +1,4 @@
{
"throws": "This experimental syntax requires enabling the parser plugin: \"decoratorAutoAccessors\". (2:11)",
"plugins": []
}
@@ -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,0 +1,41 @@
{
"type": "File",
"start":0,"end":29,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"program": {
"type": "Program",
"start":0,"end":29,"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ClassDeclaration",
"start":0,"end":29,"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":29,"loc":{"start":{"line":1,"column":10},"end":{"line":3,"column":1}},
"body": [
{
"type": "ClassAccessorProperty",
"start":14,"end":27,"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":15}},
"static": false,
"key": {
"type": "Identifier",
"start":23,"end":26,"loc":{"start":{"line":2,"column":11},"end":{"line":2,"column":14},"identifierName":"bar"},
"name": "bar"
},
"computed": false,
"value": null
}
]
}
}
],
"directives": []
}
}
@@ -0,0 +1,3 @@
class Foo {
accessor ["constructor"];
}

0 comments on commit ee06a98

Please sign in to comment.