Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Commit

Permalink
Modify grammar to support Private Fields proposal: (#260)
Browse files Browse the repository at this point in the history
* Modify grammar to support Private Fields proposal:
- Adding optional plugin `classPrivateProperties`
- Adding PrivateName type identifier
- Adding ClassPrivateProperty to ClassBody
- Allow PrivateName in MemberExpression
- Allow PrivateName as a reference
- Adding tests

* Remove unnecesary liberal parameter

* Guarding for plugin dependecy for future versioning

* update spec.md [skip ci]

* move comment [skip ci]

* remove unused param [skip ci]

* Refactor PrivateName to contain Identifier in name property
  • Loading branch information
diervo authored and hzoo committed May 22, 2017
1 parent 6c4acec commit 01da622
Show file tree
Hide file tree
Showing 26 changed files with 4,148 additions and 5 deletions.
26 changes: 25 additions & 1 deletion ast/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ These are the core Babylon AST node types.
- [Node objects](#node-objects)
- [Changes](#changes)
- [Identifier](#identifier)
- [PrivateName](#privatename)
- [Literals](#literals)
- [RegExpLiteral](#regexpliteral)
- [NullLiteral](#nullliteral)
Expand Down Expand Up @@ -90,6 +91,7 @@ These are the core Babylon AST node types.
- [ClassBody](#classbody)
- [ClassMethod](#classmethod)
- [ClassProperty](#classproperty)
- [ClassPrivateProperty](#classprivateproperty)
- [ClassDeclaration](#classdeclaration)
- [ClassExpression](#classexpression)
- [MetaProperty](#metaproperty)
Expand Down Expand Up @@ -166,6 +168,18 @@ interface Identifier <: Expression, Pattern {

An identifier. Note that an identifier may be an expression or a destructuring pattern.


# PrivateName

```js
interface PrivateName <: Expression, Pattern {
type: "PrivateName";
name: Identifier;
}
```
A Private Name Identifier.


# Literals

```js
Expand Down Expand Up @@ -1015,7 +1029,7 @@ interface Class <: Node {
```js
interface ClassBody <: Node {
type: "ClassBody";
body: [ ClassMethod | ClassProperty ];
body: [ ClassMethod | ClassProperty | ClassPrivateProperty ];
}
```

Expand Down Expand Up @@ -1043,6 +1057,16 @@ interface ClassProperty <: Node {
}
```

## ClassPrivateProperty

```js
interface ClassPrivateProperty <: Node {
type: "ClassPrivateProperty";
key: Identifier;
value: Expression;
}
```

## ClassDeclaration

```js
Expand Down
22 changes: 20 additions & 2 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export default class ExpressionParser extends LValParser {
parseMaybeAssign(noIn?: ?boolean, refShorthandDefaultPos?: ?Pos, afterLeftParse?: Function, refNeedsArrowPos?: ?Pos): N.Expression {
const startPos = this.state.start;
const startLoc = this.state.startLoc;

if (this.match(tt._yield) && this.state.inGenerator) {
let left = this.parseYield();
if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc);
Expand Down Expand Up @@ -297,7 +296,7 @@ export default class ExpressionParser extends LValParser {
} else if (this.eat(tt.dot)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
node.property = this.parseIdentifier(true);
node.property = this.hasPlugin("classPrivateProperties") ? this.parseMaybePrivateName() : this.parseIdentifier(true);
node.computed = false;
base = this.finishNode(node, "MemberExpression");
} else if (this.eat(tt.bracketL)) {
Expand Down Expand Up @@ -525,6 +524,13 @@ export default class ExpressionParser extends LValParser {
this.takeDecorators(node);
return this.parseClass(node, false);

case tt.hash:
if (this.hasPlugin("classPrivateProperties")) {
return this.parseMaybePrivateName();
} else {
this.unexpected();
}

case tt._new:
return this.parseNew();

Expand All @@ -547,6 +553,18 @@ export default class ExpressionParser extends LValParser {
}
}

parseMaybePrivateName(): N.PrivateName | N.Identifier {
const isPrivate = this.eat(tt.hash);

if (isPrivate) {
const node = this.startNode();
node.name = this.parseIdentifier(true);
return this.finishNode(node, "PrivateName");
} else {
return this.parseIdentifier(true);
}
}

parseFunctionExpression(): N.FunctionExpression | N.MetaProperty {
const node = this.startNode();
const meta = this.parseIdentifier(true);
Expand Down
2 changes: 2 additions & 0 deletions src/parser/lval.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class LValParser extends NodeUtils {
if (node) {
switch (node.type) {
case "Identifier":
case "PrivateName":
case "ObjectPattern":
case "ArrayPattern":
case "AssignmentPattern":
Expand Down Expand Up @@ -227,6 +228,7 @@ export default class LValParser extends NodeUtils {
checkClashes: ?{ [key: string]: boolean },
contextDescription: string): void {
switch (expr.type) {
case "PrivateName":
case "Identifier":
this.checkReservedWord(expr.name, expr.start, false, true);

Expand Down
25 changes: 25 additions & 0 deletions src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ export default class StatementParser extends ExpressionParser {
// class bodies are implicitly strict
const oldStrict = this.state.strict;
this.state.strict = true;
this.state.inClass = true;

let hadConstructor = false;
let decorators = [];
Expand Down Expand Up @@ -680,6 +681,13 @@ export default class StatementParser extends ExpressionParser {
decorators = [];
}

if (this.hasPlugin("classPrivateProperties") && this.match(tt.hash)) { // Private property
this.next();
this.parsePropertyName(method);
classBody.body.push(this.parsePrivateClassProperty(method));
continue;
}

method.static = false;
if (this.match(tt.name) && this.state.value === "static") {
const key = this.parseIdentifier(true); // eats 'static'
Expand Down Expand Up @@ -774,9 +782,26 @@ export default class StatementParser extends ExpressionParser {

node.body = this.finishNode(classBody, "ClassBody");

this.state.inClass = false;
this.state.strict = oldStrict;

}

parsePrivateClassProperty(node: N.ClassPrivateProperty): N.ClassPrivateProperty {
this.state.inClassProperty = true;

if (this.match(tt.eq)) {
this.next();
node.value = this.parseMaybeAssign();
} else {
node.value = null;
}
this.semicolon();
this.state.inClassProperty = false;
return this.finishNode(node, "ClassPrivateProperty");
}


parseClassProperty(node: N.ClassProperty): N.ClassProperty {
const hasPlugin = this.hasPlugin("classProperties");
const noPluginMsg = "You can only use Class Properties when the 'classProperties' plugin is enabled.";
Expand Down
9 changes: 9 additions & 0 deletions src/tokenizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,8 +405,17 @@ export default class Tokenizer extends LocationParser {

getTokenFromCode(code: number): void {
switch (code) {

case 35: // '#'
if (this.hasPlugin("classPrivateProperties") && this.state.inClass) {
++this.state.pos; return this.finishToken(tt.hash);
} else {
this.raise(this.state.pos, `Unexpected character '${codePointToString(code)}'`);
}

// The interpretation of a dot depends on whether it is followed
// by a digit or another two dots.

case 46: // '.'
return this.readToken_dot();

Expand Down
2 changes: 2 additions & 0 deletions src/tokenizer/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default class State {
this.inAsync =
this.inPropertyName =
this.inType =
this.inClass =
this.inClassProperty =
this.noAnonFunctionType =
false;
Expand Down Expand Up @@ -81,6 +82,7 @@ export default class State {
noAnonFunctionType: boolean;
inPropertyName: boolean;
inClassProperty: boolean;
inClass: boolean;

// Labels in scope.
labels: Array<{ kind: ?("loop" | "switch"), statementStart?: number }>;
Expand Down
1 change: 1 addition & 0 deletions src/tokenizer/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const types: { [name: string]: TokenType } = {
backQuote: new TokenType("`", { startsExpr }),
dollarBraceL: new TokenType("${", { beforeExpr, startsExpr }),
at: new TokenType("@"),
hash: new TokenType("#"),

// Operators. These carry several kinds of properties to help the
// parser use them properly (the presence of these properties is
Expand Down
23 changes: 21 additions & 2 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export type Identifier = PatternBase & {
__clone(): Identifier;
};

export type PrivateName = PatternBase & {
type: "PrivateName";
name: string;

__clone(): Identifier;
};

// Literals

export type Literal = RegExpLiteral | NullLiteral | StringLiteral | BooleanLiteral | NumericLiteral;
Expand Down Expand Up @@ -334,7 +341,7 @@ export type ObjectExpression = NodeBase & {
properties: $ReadOnlyArray<ObjectProperty | ObjectMethod | SpreadElement>;
};

export type ObjectOrClassMember = ClassMethod | ClassProperty | ObjectMember;
export type ObjectOrClassMember = ClassMethod | ClassProperty | ClassPrivateProperty | ObjectMember;

export type ObjectMember = ObjectProperty | ObjectMethod;

Expand Down Expand Up @@ -552,7 +559,7 @@ export type ClassMemberBase = NodeBase & HasDecorators & {

export type Accessibility = "public" | "protected" | "private";

export type ClassMember = ClassMethod | ClassProperty;
export type ClassMember = ClassMethod | ClassProperty | ClassPrivateProperty;

export type MethodLike = ObjectMethod | FunctionExpression | ClassMethod;

Expand Down Expand Up @@ -584,6 +591,18 @@ export type ClassProperty = ClassMemberBase & {
readonly?: true;
};

export type ClassPrivateProperty = ClassMemberBase & {
type: "ClassPrivateProperty";
key: Identifier;
value: ?Expression; // TODO: Not in spec that this is nullable.

typeAnnotation?: ?FlowTypeAnnotation; // TODO: Not in spec
variance?: ?FlowVariance; // TODO: Not in spec

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

export type OptClassDeclaration = ClassBase & DeclarationBase & HasDecorators & {
type: "ClassDeclaration";
// TypeScript only
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Foo {
#p = x
[#m] () {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"throws": "Unexpected token, expected ; (3:10)",
"plugins": [
"classProperties",
"classPrivateProperties"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Foo {
#p = x
*#m () {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"throws": "Unexpected token, expected ; (3:9)",
"plugins": ["classProperties", "classPrivateProperties"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo {
#x #y
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"throws": "Unexpected token, expected ; (2:5)",
"plugins": ["classProperties", "classPrivateProperties"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Foo {
#x
#y
}
Loading

0 comments on commit 01da622

Please sign in to comment.