Extract 'parseClassMember' method (#533)

This commit is contained in:
Andy 2017-05-26 20:44:56 -07:00 committed by Henry Zhu
parent aad95c63ec
commit 50694f99b1
4 changed files with 120 additions and 102 deletions

View File

@ -887,7 +887,7 @@ export default class ExpressionParser extends LValParser {
// get methods aren't allowed to have any parameters
// set methods must have exactly 1 parameter
checkGetterSetterParamCount(method: N.ObjectMethod): void {
checkGetterSetterParamCount(method: N.ObjectMethod | N.ClassMethod): void {
const paramCount = method.kind === "get" ? 0 : 1;
if (method.params.length !== paramCount) {
const start = method.start;
@ -956,12 +956,14 @@ export default class ExpressionParser extends LValParser {
if (!node) this.unexpected();
}
parsePropertyName(prop: N.ObjectMember): N.Identifier {
parsePropertyName(prop: N.ObjectOrClassMember): N.Identifier {
if (this.eat(tt.bracketL)) {
// $FlowFixMe (ClassPrivateMember shouldn't be allowed to be computed!)
prop.computed = true;
prop.key = this.parseMaybeAssign();
this.expect(tt.bracketR);
} else {
// $FlowFixMe (ClassPrivateMember shouldn't be allowed to be computed!)
prop.computed = false;
const oldInPropertyName = this.state.inPropertyName;
this.state.inPropertyName = true;

View File

@ -639,9 +639,11 @@ export default class StatementParser extends ExpressionParser {
return this.match(tt.parenL);
}
isNonstaticConstructor(method: N.ClassMethod): boolean {
isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean {
return !method.computed && !method.static && (
// $FlowFixMe ('key' downcasting)
(method.key.name === "constructor") || // Identifier
// $FlowFixMe ('key' downcasting)
(method.key.value === "constructor") // Literal
);
}
@ -652,7 +654,7 @@ export default class StatementParser extends ExpressionParser {
this.state.strict = true;
this.state.inClass = true;
let hadConstructor = false;
const state = { hadConstructor: false };
let decorators = [];
const classBody = this.startNode();
@ -673,107 +675,15 @@ export default class StatementParser extends ExpressionParser {
continue;
}
const method = this.startNode();
const member = this.startNode();
// steal the decorators if there are any
if (decorators.length) {
method.decorators = decorators;
member.decorators = decorators;
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'
if (this.isClassMethod()) {
// a method named 'static'
method.kind = "method";
method.computed = false;
method.key = key;
this.parseClassMethod(classBody, method, false, false);
continue;
} else if (this.isClassProperty()) {
// a property named 'static'
method.computed = false;
method.key = key;
classBody.body.push(this.parseClassProperty(method));
continue;
}
// otherwise something static
method.static = true;
}
if (this.eat(tt.star)) {
// a generator
method.kind = "method";
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't be a generator");
}
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
this.raise(method.key.start, "Classes may not have static property named prototype");
}
this.parseClassMethod(classBody, method, true, false);
} else {
const isSimple = this.match(tt.name);
const key = this.parsePropertyName(method);
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
this.raise(method.key.start, "Classes may not have static property named prototype");
}
if (this.isClassMethod()) {
// a normal method
if (this.isNonstaticConstructor(method)) {
if (hadConstructor) {
this.raise(key.start, "Duplicate constructor in the same class");
} else if (method.decorators) {
this.raise(method.start, "You can't attach decorators to a class constructor");
}
hadConstructor = true;
method.kind = "constructor";
} else {
method.kind = "method";
}
this.parseClassMethod(classBody, method, false, false);
} else if (this.isClassProperty()) {
// a normal property
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Classes may not have a non-static field named 'constructor'");
}
classBody.body.push(this.parseClassProperty(method));
} else if (isSimple && key.name === "async" && !this.isLineTerminator()) {
// an async method
const isGenerator = this.hasPlugin("asyncGenerators") && this.eat(tt.star);
method.kind = "method";
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't be an async function");
}
this.parseClassMethod(classBody, method, isGenerator, true);
} else if (isSimple && (key.name === "get" || key.name === "set") && !(this.isLineTerminator() && this.match(tt.star))) { // `get\n*` is an uninitialized property named 'get' followed by a generator.
// a getter or setter
method.kind = key.name;
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't have get/set modifier");
}
this.parseClassMethod(classBody, method, false, false);
this.checkGetterSetterParamCount(method);
} else if (this.isLineTerminator()) {
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Classes may not have a non-static field named 'constructor'");
}
classBody.body.push(this.parseClassProperty(method));
} else {
this.unexpected();
}
}
this.parseClassMember(classBody, member, state);
}
if (decorators.length) {
@ -784,7 +694,111 @@ export default class StatementParser extends ExpressionParser {
this.state.inClass = false;
this.state.strict = oldStrict;
}
parseClassMember(classBody: N.ClassBody, member: N.ClassMember, state: { hadConstructor: boolean }): void {
// Use the appropriate variable to represent `member` once a more specific type is known.
const memberAny: any = member;
const methodOrProp: N.ClassMethod | N.ClassProperty = memberAny;
const method: N.ClassMethod = memberAny;
const prop: N.ClassProperty = memberAny;
if (this.hasPlugin("classPrivateProperties") && this.match(tt.hash)) { // Private property
this.next();
const privateProp: N.ClassPrivateProperty = memberAny;
this.parsePropertyName(privateProp);
classBody.body.push(this.parsePrivateClassProperty(privateProp));
return;
}
methodOrProp.static = false;
if (this.match(tt.name) && this.state.value === "static") {
const key = this.parseIdentifier(true); // eats 'static'
if (this.isClassMethod()) {
// a method named 'static'
method.kind = "method";
method.computed = false;
method.key = key;
this.parseClassMethod(classBody, method, false, false);
return;
} else if (this.isClassProperty()) {
// a property named 'static'
prop.computed = false;
prop.key = key;
classBody.body.push(this.parseClassProperty(prop));
return;
}
// otherwise something static
methodOrProp.static = true;
}
if (this.eat(tt.star)) {
// a generator
method.kind = "method";
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't be a generator");
}
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
this.raise(method.key.start, "Classes may not have static property named prototype");
}
this.parseClassMethod(classBody, method, true, false);
return;
}
const isSimple = this.match(tt.name);
const key = this.parsePropertyName(methodOrProp);
// $FlowFixMe ('key' downcasting)
if (!methodOrProp.computed && methodOrProp.static && (methodOrProp.key.name === "prototype" || methodOrProp.key.value === "prototype")) {
this.raise(methodOrProp.key.start, "Classes may not have static property named prototype");
}
if (this.isClassMethod()) {
// a normal method
if (this.isNonstaticConstructor(method)) {
if (state.hadConstructor) {
this.raise(key.start, "Duplicate constructor in the same class");
} else if (method.decorators) {
this.raise(method.start, "You can't attach decorators to a class constructor");
}
state.hadConstructor = true;
method.kind = "constructor";
} else {
method.kind = "method";
}
this.parseClassMethod(classBody, method, false, false);
} else if (this.isClassProperty()) {
// a normal property
if (this.isNonstaticConstructor(prop)) {
this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'");
}
classBody.body.push(this.parseClassProperty(prop));
} else if (isSimple && key.name === "async" && !this.isLineTerminator()) {
// an async method
const isGenerator = this.hasPlugin("asyncGenerators") && this.eat(tt.star);
method.kind = "method";
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't be an async function");
}
this.parseClassMethod(classBody, method, isGenerator, true);
} else if (isSimple && (key.name === "get" || key.name === "set") && !(this.isLineTerminator() && this.match(tt.star))) { // `get\n*` is an uninitialized property named 'get' followed by a generator.
// a getter or setter
method.kind = key.name;
this.parsePropertyName(method);
if (this.isNonstaticConstructor(method)) {
this.raise(method.key.start, "Constructor can't have get/set modifier");
}
this.parseClassMethod(classBody, method, false, false);
this.checkGetterSetterParamCount(method);
} else if (this.isLineTerminator()) {
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
if (this.isNonstaticConstructor(prop)) {
this.raise(prop.key.start, "Classes may not have a non-static field named 'constructor'");
}
classBody.body.push(this.parseClassProperty(prop));
} else {
this.unexpected();
}
}
parsePrivateClassProperty(node: N.ClassPrivateProperty): N.ClassPrivateProperty {

View File

@ -59,8 +59,9 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
}
}
checkGetterSetterParamCount(prop: N.ObjectMethod): void {
checkGetterSetterParamCount(prop: N.ObjectMethod | N.ClassMethod): void {
const paramCount = prop.kind === "get" ? 0 : 1;
// $FlowFixMe (prop.value present for ObjectMethod, but for ClassMethod should use prop.params?)
if (prop.value.params.length !== paramCount) {
const start = prop.start;
if (prop.kind === "get") {

View File

@ -1242,7 +1242,7 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
return this.match(tt.colon) || super.isClassProperty();
}
isNonstaticConstructor(method: N.ClassMethod): boolean {
isNonstaticConstructor(method: N.ClassMethod | N.ClassProperty): boolean {
return !this.match(tt.colon) && super.isNonstaticConstructor(method);
}
@ -1281,9 +1281,10 @@ export default (superClass: Class<Parser>): Class<Parser> => class extends super
}
}
parsePropertyName(node: N.ObjectMember): N.Identifier {
parsePropertyName(node: N.ObjectOrClassMember): N.Identifier {
const variance = this.flowParseVariance();
const key = super.parsePropertyName(node);
// $FlowFixMe (variance not defined on ClassPrivateProperty)
node.variance = variance;
return key;
}