-
-
Notifications
You must be signed in to change notification settings - Fork 258
Fix several issues with class properties (#157, #116) #158
Conversation
Current coverage is 94.59% (diff: 100%)@@ master #158 diff @@
==========================================
Files 19 19
Lines 3135 3144 +9
Methods 328 331 +3
Messages 0 0
Branches 828 833 +5
==========================================
+ Hits 2963 2974 +11
Misses 94 94
+ Partials 78 76 -2
|
Not sure if I count as a higher authority, but it is absolutely the case that no ASI can occur after See also other committee members on the same topic. Also, the issue is larger than I initially reported and is not covered by this fix. All of the following are incorrectly rejected: class C {
x
(){}
} class C {
get
(){}
} class C {
set
(){}
} class C {
static
(){}
} class C {
async
(){}
} etc., which define methods named 'x', 'get', 'set', 'static', or 'async'. EDIT: same issue when the name is |
Haven't looked into the PR implementation, but I'll verify it seems like the newline should be allowed there since the spec explicitly marks things with |
I will try to fix them here and report back. On Oct 12, 2016 11:53, "Daniel Tschinder" [email protected] wrote:
|
Looking into this now. By the way, |
Yeah, the fun edge case for class C {
async
x(){}
} which should have ASI performed, such that it parses the same as class C {
async;
x(){}
} i.e., a syntax error if class properties are not enabled, and otherwise an uninitialized class property named Contrast class C {
static
x(){}
} which is a static method named IIRC, Babylon gets these three cases right. |
OK, this was as simple as bailing out from a ClassProperty parse if the name is followed by a left paren. I've also added tests to cover the various cases raised here. |
I don't think that's going to be quite sufficient - e.g. this is a class with just a property named 'get': This patch as it stands regresses 'get' and 'set' there and handles 'async' correctly; it does not correctly handle 'static', but neither does master. |
While I'm at it... Class properties can appear on the same line if separated by semicolons. So Sorry for continuing to come back with more issues; I'm just mentioning them as I think of them and/or recall them. As long as this patch doesn't regress any such cases, I would expect it to be fine to handle this issue in another patch. |
@bakkot Personally I'm very grateful for your input. If I'm working on this part of the code, might as well get it right and not just fix one edge case 😃 |
Hang on, I'm not comfortable with us emitting ClassProperty nodes without the |
This is so we don't break if babel/babylon#158 lands.
Well! I'm quite possibly done. Come on, hit this with all you've got 💪 |
classBody.body.push(this.parseClassProperty(method)); | ||
continue; | ||
if (this.isInitializedClassProperty() || (this.isUninitializedClassProperty() && !(isMaybeGetSet && this.match(tt.name)))) { | ||
if (this.hasPlugin("classProperties") || this.hasPlugin("flow")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not involved in the project, so take this with a grain of salt, but nested if
s like this look strange to me - why not combine the two conditions?
Issues: rejects class C {
get
[Symbol.foo](){}
} and class C {
static
get
x(){}
} as a property and a method instead of as a getter. Might also be good to add a test for class C {
get
*x(){}
} (a property followed by a generator method), which is handled correctly but which is kind of a funny case. Otherwise looks good to me. |
@bakkot That first one mayyyy be out of scope here but I'd like to fix it too, with the same rationale as above. This is getting nightmarish - what about this one? class C {
static
get
static
(){}
} This is a static getter named static, right? |
@motiz88, indeed it is. I think you pretty much have to end up repeatedly inspecting the next token to determine the type of the property. Something like this (forgive the psuedocode / difference from Babylon; I'm not quite awake and haven't read the project source): function parsePropertyName() {...} // should include static and computed names
let static = false;
if (eat('static')) {
switch (lookahead) { // assuming 'lookahead' magically holds the next token
case '(':
return parseClassMethod({name: 'static', static, async: false});
case '=':
return parseInitializedClassProperty({name: 'static', static});
case ';':
case '}':
return parseUninitializedClassProperty({name: 'static', static});
}
static = true;
}
if (eat('*')) {
let name = parsePropertyName();
return parseGeneratorMethod({name, static});
}
if (eat('async')) {
switch (lookahead) {
case '(':
return parseClassMethod({name: 'async', static, async: false});
case '=':
return parseInitializedClassProperty({name: 'async', static});
case ';':
case '}':
return parseUninitializedClassProperty({name: 'async', static});
}
if (hasLineTerminatorBeforeNext) {
return parseUninitializedClassProperty({name: 'async', static});
}
let name = parsePropertyName();
return parseClassMethod({name, static, async: true});
}
if (eat('get')) {
switch (lookahead) {
case '(':
return parseClassMethod({name: 'get', static, async: false});
case '=':
return parseInitializedClassProperty({name: 'get', static});
case ';':
case '}':
return parseUninitializedClassProperty({name: 'get', static});
case '*':
if (hasLineTerminatorBeforeNext) {
return parseUninitializedClassProperty({name: 'get', static});
} else {
throw new SyntaxError('unexpected token');
}
}
let name = parsePropertyName();
return parseGetter({name, static});
}
if (eat('set')) {
switch (lookahead) {
case '(':
return parseClassMethod({name: 'set', static, async: false});
case '=':
return parseInitializedClassProperty({name: 'set', static});
case ';':
case '}':
return parseUninitializedClassProperty({name: 'set', static});
case '*':
if (hasLineTerminatorBeforeNext) {
return parseUninitializedClassProperty({name: 'set', static});
} else {
throw new SyntaxError('unexpected token');
}
}
let name = parsePropertyName();
return parseSetter({name, static});
}
let name = parsePropertyName();
switch (lookahead) {
case '(':
return parseClassMethod({name, static, async: false});
case '=':
return parseInitializedClassProperty({name, static});
}
return parseUninitializedClassProperty({name, static}); |
@bakkot Yeah, looks like a rewrite for sanity is in order and your pseudocode will be a lifesaver. Thanks! Will get to it in a day or two. |
Hey @motiz88 What is the status on this? can this be reviewed and merged or is there still stuff open? |
@danez - this is still in need of a major rewrite. I unexpectedly ran out of time to devote to this issue, and this will go on for a few weeks at least. As is, the PR solves some edge cases but opens up others, and I've come to realize that larger changes would be required for the code to be both correct and maintainable. Hence a rewrite. |
@motiz88, still planning on getting to this? I expect I could take it if not. |
I believe @motiz88 has been pretty busy over the last few months so dono when ready to get back into oss stuff |
obsolete as of #351 |
EDIT: This rolls up fixes and tests for several interrelated bugs detailed in issues #157 and #116 as well as in the comments below, namely:
get
/set
in a class (resulting in a property + regular method);classProperties
being set (except whenflow
is set, which should enable uninitialized properties only)To repeat the note I made in #157: All parsers on ast-explorer.net, except for TypeScript, disagree with Babylon's current parse and agree with this PR (EDIT: this was true as of ca66f6b and refers only to the initial
get
/set
issue from #157 - with the later fixes we've diverged from e.g. Flow)I'm not saying it's necessarily correct, though. Definitely could use input from a higher authority on spec matters.