Skip to content

Commit 81056ee

Browse files
bakkotdanez
authored andcommitted
Fix parsing of class properties (babel#351)
1 parent 0b7da50 commit 81056ee

File tree

47 files changed

+3820
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3820
-94
lines changed

src/parser/statement.js

+97-80
Original file line numberDiff line numberDiff line change
@@ -625,11 +625,18 @@ pp.parseClass = function (node, isStatement, optionalId) {
625625
};
626626

627627
pp.isClassProperty = function () {
628-
return this.match(tt.eq) || this.isLineTerminator();
628+
return this.match(tt.eq) || this.match(tt.semi) || this.match(tt.braceR);
629629
};
630630

631-
pp.isClassMutatorStarter = function () {
632-
return false;
631+
pp.isClassMethod = function () {
632+
return this.match(tt.parenL);
633+
};
634+
635+
pp.isNonstaticConstructor = function (method) {
636+
return !method.computed && !method.static && (
637+
(method.key.name === "constructor") || // Identifier
638+
(method.key.value === "constructor") // Literal
639+
);
633640
};
634641

635642
pp.parseClassBody = function (node) {
@@ -667,92 +674,102 @@ pp.parseClassBody = function (node) {
667674
decorators = [];
668675
}
669676

670-
let isConstructorCall = false;
671-
const isMaybeStatic = this.match(tt.name) && this.state.value === "static";
672-
let isGenerator = this.eat(tt.star);
673-
let isGetSet = false;
674-
let isAsync = false;
675-
676-
this.parsePropertyName(method);
677-
678-
method.static = isMaybeStatic && !this.match(tt.parenL);
679-
if (method.static) {
680-
isGenerator = this.eat(tt.star);
681-
this.parsePropertyName(method);
682-
}
683-
684-
if (!isGenerator) {
685-
if (this.isClassProperty()) {
677+
method.static = false;
678+
if (this.match(tt.name) && this.state.value === "static") {
679+
const key = this.parseIdentifier(true); // eats 'static'
680+
if (this.isClassMethod()) {
681+
// a method named 'static'
682+
method.kind = "method";
683+
method.computed = false;
684+
method.key = key;
685+
this.parseClassMethod(classBody, method, false, false);
686+
continue;
687+
} else if (this.isClassProperty()) {
688+
// a property named 'static'
689+
method.computed = false;
690+
method.key = key;
686691
classBody.body.push(this.parseClassProperty(method));
687692
continue;
688693
}
689-
690-
if (method.key.type === "Identifier" && !method.computed && this.hasPlugin("classConstructorCall") && method.key.name === "call" && this.match(tt.name) && this.state.value === "constructor") {
691-
isConstructorCall = true;
692-
this.parsePropertyName(method);
693-
}
694+
// otherwise something static
695+
method.static = true;
694696
}
695697

696-
const isAsyncMethod = !this.match(tt.parenL) && !method.computed && method.key.type === "Identifier" && method.key.name === "async";
697-
if (isAsyncMethod) {
698-
if (this.hasPlugin("asyncGenerators") && this.eat(tt.star)) isGenerator = true;
699-
isAsync = true;
698+
if (this.eat(tt.star)) {
699+
// a generator
700+
method.kind = "method";
700701
this.parsePropertyName(method);
701-
}
702-
703-
method.kind = "method";
704-
705-
if (!method.computed) {
706-
let { key } = method;
707-
708-
// handle get/set methods
709-
// eg. class Foo { get bar() {} set bar() {} }
710-
if (!isAsync && !isGenerator && !this.isClassMutatorStarter() && key.type === "Identifier" && !this.match(tt.parenL) && (key.name === "get" || key.name === "set")) {
711-
isGetSet = true;
712-
method.kind = key.name;
713-
key = this.parsePropertyName(method);
702+
if (this.isNonstaticConstructor(method)) {
703+
this.raise(method.key.start, "Constructor can't be a generator");
714704
}
715-
716-
// disallow invalid constructors
717-
const isConstructor = !isConstructorCall && !method.static && (
718-
(key.name === "constructor") || // Identifier
719-
(key.value === "constructor") // Literal
720-
);
721-
if (isConstructor) {
722-
if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class");
723-
if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier");
724-
if (isGenerator) this.raise(key.start, "Constructor can't be a generator");
725-
if (isAsync) this.raise(key.start, "Constructor can't be an async function");
726-
method.kind = "constructor";
727-
hadConstructor = true;
705+
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
706+
this.raise(method.key.start, "Classes may not have static property named prototype");
728707
}
729-
730-
// disallow static prototype method
731-
const isStaticPrototype = method.static && (
732-
(key.name === "prototype") || // Identifier
733-
(key.value === "prototype") // Literal
734-
);
735-
if (isStaticPrototype) {
736-
this.raise(key.start, "Classes may not have static property named prototype");
708+
this.parseClassMethod(classBody, method, true, false);
709+
} else {
710+
const isSimple = this.match(tt.name);
711+
const key = this.parsePropertyName(method);
712+
if (!method.computed && method.static && (method.key.name === "prototype" || method.key.value === "prototype")) {
713+
this.raise(method.key.start, "Classes may not have static property named prototype");
714+
}
715+
if (this.isClassMethod()) {
716+
// a normal method
717+
if (this.isNonstaticConstructor(method)) {
718+
if (hadConstructor) {
719+
this.raise(key.start, "Duplicate constructor in the same class");
720+
} else if (method.decorators) {
721+
this.raise(method.start, "You can't attach decorators to a class constructor");
722+
}
723+
hadConstructor = true;
724+
method.kind = "constructor";
725+
} else {
726+
method.kind = "method";
727+
}
728+
this.parseClassMethod(classBody, method, false, false);
729+
} else if (this.isClassProperty()) {
730+
// a normal property
731+
if (this.isNonstaticConstructor(method)) {
732+
this.raise(method.key.start, "Classes may not have a non-static field named 'constructor'");
733+
}
734+
classBody.body.push(this.parseClassProperty(method));
735+
} else if (isSimple && key.name === "async" && !this.isLineTerminator()) {
736+
// an async method
737+
const isGenerator = this.hasPlugin("asyncGenerators") && this.eat(tt.star);
738+
method.kind = "method";
739+
this.parsePropertyName(method);
740+
if (this.isNonstaticConstructor(method)) {
741+
this.raise(method.key.start, "Constructor can't be an async function");
742+
}
743+
this.parseClassMethod(classBody, method, isGenerator, true);
744+
} 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.
745+
// a getter or setter
746+
method.kind = key.name;
747+
this.parsePropertyName(method);
748+
if (this.isNonstaticConstructor(method)) {
749+
this.raise(method.key.start, "Constructor can't have get/set modifier");
750+
}
751+
this.parseClassMethod(classBody, method, false, false);
752+
this.checkGetterSetterParamCount(method);
753+
} else if (this.hasPlugin("classConstructorCall") && isSimple && key.name === "call" && this.match(tt.name) && this.state.value === "constructor") {
754+
// a (deprecated) call constructor
755+
if (hadConstructorCall) {
756+
this.raise(method.start, "Duplicate constructor call in the same class");
757+
} else if (method.decorators) {
758+
this.raise(method.start, "You can't attach decorators to a class constructor");
759+
}
760+
hadConstructorCall = true;
761+
method.kind = "constructorCall";
762+
this.parsePropertyName(method); // consume "constructor" and make it the method's name
763+
this.parseClassMethod(classBody, method, false, false);
764+
} else if (this.isLineTerminator()) {
765+
// an uninitialized class property (due to ASI, since we don't otherwise recognize the next token)
766+
if (this.isNonstaticConstructor(method)) {
767+
this.raise(method.key.start, "Classes may not have a non-static field named 'constructor'");
768+
}
769+
classBody.body.push(this.parseClassProperty(method));
770+
} else {
771+
this.unexpected();
737772
}
738-
}
739-
740-
// convert constructor to a constructor call
741-
if (isConstructorCall) {
742-
if (hadConstructorCall) this.raise(method.start, "Duplicate constructor call in the same class");
743-
method.kind = "constructorCall";
744-
hadConstructorCall = true;
745-
}
746-
747-
// disallow decorators on class constructors
748-
if ((method.kind === "constructor" || method.kind === "constructorCall") && method.decorators) {
749-
this.raise(method.start, "You can't attach decorators to a class constructor");
750-
}
751-
752-
this.parseClassMethod(classBody, method, isGenerator, isAsync);
753-
754-
if (isGetSet) {
755-
this.checkGetterSetterParamCount(method);
756773
}
757774
}
758775

src/plugins/flow.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,13 @@ export default function (instance) {
11091109
};
11101110
});
11111111

1112+
// determine whether or not we're currently in the position where a class method would appear
1113+
instance.extend("isClassMethod", function (inner) {
1114+
return function () {
1115+
return this.isRelational("<") || inner.call(this);
1116+
};
1117+
});
1118+
11121119
// determine whether or not we're currently in the position where a class property would appear
11131120
instance.extend("isClassProperty", function (inner) {
11141121
return function () {
@@ -1439,14 +1446,4 @@ export default function (instance) {
14391446
return this.match(tt.colon) || inner.call(this);
14401447
};
14411448
});
1442-
1443-
instance.extend("isClassMutatorStarter", function (inner) {
1444-
return function () {
1445-
if (this.isRelational("<")) {
1446-
return true;
1447-
} else {
1448-
return inner.call(this);
1449-
}
1450-
};
1451-
});
14521449
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class A {
2+
static *prototype() {}
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"throws": "Classes may not have static property named prototype (2:10)"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class A {
2+
get
3+
a
4+
() {}
5+
6+
set
7+
a
8+
(a) {}
9+
10+
constructor
11+
() {}
12+
13+
a
14+
() {}
15+
16+
*
17+
a
18+
() {}
19+
20+
static
21+
get
22+
a
23+
() {}
24+
25+
static
26+
set
27+
a
28+
(a) {}
29+
30+
static
31+
constructor
32+
() {}
33+
34+
static
35+
a
36+
() {}
37+
38+
static
39+
*
40+
a
41+
() {}
42+
}

0 commit comments

Comments
 (0)