Add support for class private methods#703
Conversation
ast/spec.md
Outdated
| type: "ClassPrivateMethod"; | ||
| key: Identifier; | ||
| kind: "method" | "get" | "set"; | ||
| computed: boolean; |
| kind: "method" | "get" | "set"; | ||
| computed: boolean; | ||
| static: boolean; | ||
| decorators: [ Decorator ]; |
There was a problem hiding this comment.
Also need async and generator flags.
There was a problem hiding this comment.
Would that be needed for the normal ClassMethod as well?
ast/spec.md
Outdated
| } | ||
| ``` | ||
|
|
||
| ## ClassMethod |
There was a problem hiding this comment.
Rename to ClassPrivateMethod
src/parser/expression.js
Outdated
| parseClassPrivateName( | ||
| prop: N.ClassPrivateProperty | N.ClassPrivateMethod, | ||
| ): N.Expression { | ||
| prop.computed = false; |
src/parser/statement.js
Outdated
| // eaten the #, but in other cases we haven't. How to solve? | ||
| this.expectPlugin("classPrivateMethods"); | ||
| // private "normal" method | ||
| method.kind = "method"; |
There was a problem hiding this comment.
Can we hoist this to the beginning of the this.isClassMethod() if statement?
There was a problem hiding this comment.
Confused about where exactly you want this part to be moved to.
src/parser/statement.js
Outdated
| const isPrivate = this.eat(tt.hash); | ||
|
|
||
| if (isPrivate) { | ||
| this.expectOnePlugin(["classPrivateProperties", "classPrivateMethods"]); |
There was a problem hiding this comment.
I think we need to do this before eating the hash.
src/parser/statement.js
Outdated
| if (this.match(tt.hash)) { | ||
| // private getter/setter | ||
| this.expectPlugin("classPrivateMethods"); | ||
| // The private # wouldn't have been eaten as the "async" was in front of it. |
There was a problem hiding this comment.
Huh? Maybe my wording was confusing.
What I'm trying to say is that it's now being eaten, but it wasn't detected as being "private" in the code above (let isPrivate = ...). Is that wrong?
There was a problem hiding this comment.
The "async" part is incorrect. We're in the "get"/"set" path here.
src/parser/statement.js
Outdated
| this.expectPlugin("classPrivateMethods"); | ||
| // The private # wouldn't have been eaten as the "async" was in front of it. | ||
| this.next(); | ||
| // The so-called parsed name would have been "async": get the real name. |
src/parser/statement.js
Outdated
| false, | ||
| /* isConstructor */ false, | ||
| ); | ||
| this.checkGetterSetterParamCount(method); |
There was a problem hiding this comment.
I think this should apply to private accessors, too.
src/types.js
Outdated
| ClassMethodOrDeclareMethodCommon & { | ||
| type: "ClassPrivateMethod", | ||
| key: Identifier, | ||
| static: false, |
There was a problem hiding this comment.
Does this need static in the first place? If not, how would I handle that in terms of types?
There was a problem hiding this comment.
Oh, it's already in ClassMethodOrDeclareMethodCommon, so we can just drop this line.
src/parser/statement.js
Outdated
| } else { | ||
| node.value = null; | ||
| } | ||
| if (node.computed !== undefined) node.computed = undefined; |
There was a problem hiding this comment.
Yeah, that was a dodgy hack for some test failure because I had set computed elsewhere by accident. I've fixed this now.
c367d80 to
d21cbfb
Compare
d21cbfb to
6e7175d
Compare
littledan
left a comment
There was a problem hiding this comment.
This seems like a correct implementation of the grammar to me!
One surprising thing to me here, which I unfortunately missed in the earlier Private Fields PR, is that there's no single AST node for a private name. Instead, an Identifier is used inside of various different node types--previously, ClassPrivateProperty and PrivateName, now also ClassPrivateMethod. I would've expected that ClassPrivateProperty and ClassPrivateMethod would contain a PrivateName which contains an Identifier, instead of directly containing the Identifier.
I assume that the private methods plugin will be responsible for throwing early errors (such as duplicate method definitions); is that right?
I appreciate the tests here. Although I can think of some more tests for syntax edge cases, maybe those are best developed in test262.
src/parser/statement.js
Outdated
|
|
||
| if (isPrivate) { | ||
| // TODO: seems a bit inconsistent error throwing: in some cases we've already | ||
| // eaten the #, but in other cases we haven't. How to solve? |
There was a problem hiding this comment.
You could make expectPlugin accept an optional pos parameter (like unexpected does) and pass node.start to it.
src/parser/statement.js
Outdated
|
|
||
| const isPrivate = this.match(tt.hash); | ||
| if (isPrivate) { | ||
| this.expectOnePlugin(["classPrivateProperties", "classPrivateMethods"]); |
There was a problem hiding this comment.
Instead of throwing here, you can wait until it is known if the node is a property or a method (to give a more specific error message).
src/parser/statement.js
Outdated
| method: N.ClassMethod, | ||
| isGenerator: boolean, | ||
| isAsync: boolean, | ||
| isConstructor: boolean, |
There was a problem hiding this comment.
No. Not only that, it should be a syntax error; see below.
There was a problem hiding this comment.
Removed (not sure why it's not saying it's outdated).
bakkot
left a comment
There was a problem hiding this comment.
If I'm reading it right, this allows methods and fields named #constructor. (That appears to have been behavior inherited from #609, which was right at the time but changed in tc39/proposal-class-fields@e7d009b.) It shouldn't, for fields or methods. Could you have parseClassPrivateName throw in that case, and add a test or two?
Otherwise looks good to me.
| // TODO: if no plugin and is PrivateName, should error be handled here or elsewhere? | ||
| if (prop.key.type !== "PrivateName") { | ||
| // ClassPrivateProperty is never computed, so we don't assign in that case. | ||
| prop.computed = false; |
There was a problem hiding this comment.
prop here is a MemberExpression? In that case, it should always be set to false.
There was a problem hiding this comment.
I think the method signature says it's a Class[Private]Property or Class[Private]Method.
src/parser/statement.js
Outdated
| if (isPrivate) { | ||
| this.expectPlugin("classPrivateProperties", key.start); | ||
| // Private property | ||
| classBody.body.push(this.parseClassPrivateProperty(prop)); |
There was a problem hiding this comment.
Can we make a #pushClassPrivateProperty?
There was a problem hiding this comment.
Done -- should the two be merged or remain separate?
src/parser/statement.js
Outdated
| method, | ||
| false, | ||
| false, | ||
| isConstructor, |
ce87518 to
5769ae5
Compare
|
I've done some updates to the PR -- will squash later once review done. While I'm at it, I'm just wondering whether the one test failure is important or not. This roughly equivalent non-private code just throws Do we need it to check whether a semicolon should be there, and if so, how would I go about doing that? |
339b4fb to
8645c96
Compare
|
@Qantas94Heavy, I don't think it's important that the failure says |
d5e0c38 to
8b5c2d5
Compare
src/parser/statement.js
Outdated
| const isSimple = key.type === "Identifier"; | ||
|
|
||
| // TODO: should these be merged into pushClassProperty? | ||
| const handleClassProperty = () => { |
There was a problem hiding this comment.
This could just be inlined into the callsites.
jridgewell
left a comment
There was a problem hiding this comment.
Oh, sorry, we need to update https://github.com/babel/babylon/pull/703/files#diff-aaeb808425fc8034ffaa66c89d53cf0aR1082. That's a PrivateName now, right?
8b5c2d5 to
09c3f50
Compare
This commit adds parser support for the TC39 Stage 2 Private Methods proposal. This commit also changes "key" in ClassPrivateProperty from an Identifier to a PrivateName, as well as disallowing #constructor as a valid private field name.
This also removes a test from the Test262 whitelist that failed before the changes for private methods support and now passes.
These should be treated as regular methods and not special get/set/async behaviour.
09c3f50 to
62233fb
Compare
|
@jridgewell right, updated. |
This adds support for private methods, as described in https://github.com/littledan/proposal-private-methods.
I haven't made an issue for this proposal yet, but if we want it to be separate from #540, I can do so.
this.hasPlugin("plugin-name-here")check so that your new plugin code only runs when that flag is turned on (not default behavior)I'll also need to submit changes to
babel-typesto match up with this PR.NOTE: There's still a little bit I'm not exactly sure about (tick means handled):
#then throws the error, while in other parts it throws the error without "eating" the#.expected ;, but the error is still thrown in the same position in the test file. (edit: changed test to match new message)