Modify grammar to support Private Fields proposal: (#260)

* 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
This commit is contained in:
Diego Ferreiro Val
2017-05-22 11:33:48 -04:00
committed by Henry Zhu
parent 6c4acecf00
commit 01da62283c
26 changed files with 4148 additions and 5 deletions

View File

@@ -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);
@@ -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)) {
@@ -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();
@@ -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);

View File

@@ -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":
@@ -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);

View File

@@ -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 = [];
@@ -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'
@@ -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.";

View File

@@ -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();

View File

@@ -24,6 +24,7 @@ export default class State {
this.inAsync =
this.inPropertyName =
this.inType =
this.inClass =
this.inClassProperty =
this.noAnonFunctionType =
false;
@@ -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 }>;

View File

@@ -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

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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