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

ClassFields: Allowing comma separated grammar #608

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 60 additions & 14 deletions src/parser/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,12 @@ export default class StatementParser extends ExpressionParser {
}

isClassProperty(): boolean {
return this.match(tt.eq) || this.match(tt.semi) || this.match(tt.braceR);
return (
this.match(tt.eq) ||
this.match(tt.semi) ||
this.match(tt.braceR) ||
this.match(tt.comma)
);
}

isClassMethod(): boolean {
Expand All @@ -849,7 +854,10 @@ export default class StatementParser extends ExpressionParser {
this.state.strict = true;
this.state.classLevel++;

const state = { hadConstructor: false };
const context = {
hadConstructor: false,
inCommaSeparatedClassFields: false,
};
let decorators: N.Decorator[] = [];
const classBody: N.ClassBody = this.startNode();

Expand All @@ -859,13 +867,18 @@ export default class StatementParser extends ExpressionParser {

while (!this.eat(tt.braceR)) {
if (this.eat(tt.semi)) {
if (context.inCommaSeparatedClassFields) {
// class { foo,; }
this.unexpected(this.state.lastTokEnd);
}

if (decorators.length > 0) {
this.raise(
this.state.lastTokEnd,
"Decorators must not be followed by a semicolon",
);
}
continue;
continue; // Go up to keep consuming `;`
}

if (this.match(tt.at)) {
Expand All @@ -877,14 +890,17 @@ export default class StatementParser extends ExpressionParser {

// steal the decorators if there are any
if (decorators.length) {
member.decorators = decorators;
if (this.hasPlugin("decorators2")) {
member.decorators = decorators.slice();
// Reset only once in case we have comma separated fields
if (
this.hasPlugin("decorators2") &&
!context.inCommaSeparatedClassFields
) {
this.resetStartLocationFromNode(member, decorators[0]);
}
decorators = [];
}

this.parseClassMember(classBody, member, state);
this.parseClassMember(classBody, member, context);

if (
this.hasPlugin("decorators2") &&
Expand All @@ -897,6 +913,20 @@ export default class StatementParser extends ExpressionParser {
"Stage 2 decorators may only be used with a class or a class method",
);
}

// Weak check for classFields
if (member.kind == null) {
context.inCommaSeparatedClassFields = this.eat(tt.comma);
}

if (context.inCommaSeparatedClassFields) {
// Check for dangling commas class Foo { x, }
if (this.match(tt.braceR)) {
this.unexpected(this.state.lastTokStart);
}
} else {
decorators = [];
}
}

if (decorators.length) {
Expand All @@ -915,7 +945,7 @@ export default class StatementParser extends ExpressionParser {
parseClassMember(
classBody: N.ClassBody,
member: N.ClassMember,
state: { hadConstructor: boolean },
context: { hadConstructor: boolean, inCommaSeparatedClassFields: boolean },
): void {
// Use the appropriate variable to represent `member` once a more specific type is known.
const memberAny: any = member;
Expand All @@ -935,6 +965,9 @@ export default class StatementParser extends ExpressionParser {
if (this.match(tt.name) && this.state.value === "static") {
const key = this.parseIdentifier(true); // eats 'static'
if (this.isClassMethod()) {
if (context.inCommaSeparatedClassFields) {
this.unexpected();
}
// a method named 'static'
method.kind = "method";
method.computed = false;
Expand All @@ -960,13 +993,13 @@ export default class StatementParser extends ExpressionParser {
isStatic = true;
}

this.parseClassMemberWithIsStatic(classBody, member, state, isStatic);
this.parseClassMemberWithIsStatic(classBody, member, context, isStatic);
}

parseClassMemberWithIsStatic(
classBody: N.ClassBody,
member: N.ClassMember,
state: { hadConstructor: boolean },
context: { hadConstructor: boolean, inCommaSeparatedClassFields: boolean },
isStatic: boolean,
) {
const memberAny: any = member;
Expand Down Expand Up @@ -1009,6 +1042,9 @@ export default class StatementParser extends ExpressionParser {
this.parsePostMemberNameModifiers(methodOrProp);

if (this.isClassMethod()) {
if (context.inCommaSeparatedClassFields) {
this.unexpected();
}
// a normal method
const isConstructor = this.isNonstaticConstructor(method);
if (isConstructor) {
Expand All @@ -1026,10 +1062,10 @@ export default class StatementParser extends ExpressionParser {
}

// TypeScript allows multiple overloaded constructor declarations.
if (state.hadConstructor && !this.hasPlugin("typescript")) {
if (context.hadConstructor && !this.hasPlugin("typescript")) {
this.raise(key.start, "Duplicate constructor in the same class");
}
state.hadConstructor = true;
context.hadConstructor = true;
}

this.parseClassMethod(classBody, method, false, false, isConstructor);
Expand Down Expand Up @@ -1135,8 +1171,10 @@ export default class StatementParser extends ExpressionParser {
} else {
node.value = null;
}
this.semicolon();

this.state.inClassProperty = false;

this.parseMaybeCommaSeparator();
return this.finishNode(node, "ClassPrivateProperty");
}

Expand All @@ -1158,8 +1196,10 @@ export default class StatementParser extends ExpressionParser {
} else {
node.value = null;
}
this.semicolon();

this.state.inClassProperty = false;

this.parseMaybeCommaSeparator();
return this.finishNode(node, "ClassProperty");
}

Expand All @@ -1181,6 +1221,12 @@ export default class StatementParser extends ExpressionParser {
);
}

parseMaybeCommaSeparator() {
if (!this.match(tt.comma)) {
this.semicolon();
}
}

parseClassId(
node: N.Class,
isStatement: boolean,
Expand Down
9 changes: 6 additions & 3 deletions src/plugins/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -1421,18 +1421,21 @@ export default (superClass: Class<Parser>): Class<Parser> =>
parseClassMember(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
context: {
hadConstructor: boolean,
inCommaSeparatedClassFields: boolean,
},
): void {
const accessibility = this.parseAccessModifier();
if (accessibility) member.accessibility = accessibility;

super.parseClassMember(classBody, member, state);
super.parseClassMember(classBody, member, context);
}

parseClassMemberWithIsStatic(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
state: { hadConstructor: boolean, inCommaSeparatedClassFields: boolean },
isStatic: boolean,
): void {
const methodOrProp: N.ClassMethod | N.ClassProperty = member;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class C {
#p1,#p2;
#p3 = 1, #p4 = 1;
#p5 = 1, #p6, #p7;
}
Loading