Optional Chaining: Stage 1 plugin#545
Optional Chaining: Stage 1 plugin#545xtuc merged 17 commits intobabel:masterfrom xtuc:feat-optional-chaining
Conversation
…fined this will return undefined. If the object on which you want to access the property is defined, the value of the propery will be given back.
…ess. (.? or ?.[) If the object is undefined this will return undefined. If the object on which you want to access the property is defined, the value of the propery will be given back.
…at-optional-chaining
Jessidhia
left a comment
There was a problem hiding this comment.
Looks OK to me, but it already has merge conflicts 😕
AST change is adding an optional boolean to MemberExpression
ast/spec.md
Outdated
| ``` | ||
|
|
||
| A member expression. If `computed` is `true`, the node corresponds to a computed (`a[b]`) member expression and `property` is an `Expression`. If `computed` is `false`, the node corresponds to a static (`a.b`) member expression and `property` is an `Identifier`. | ||
| A member expression. If `computed` is `true`, the node corresponds to a computed (`a[b]`) member expression and `property` is an `Expression`. If `computed` is `false`, the node corresponds to a static (`a.b`) member expression and `property` is an `Identifier`. The `optional` flags indecates that the member expression can be called even if the object is null or undefined. If this is the object value (null/undefined) should be returned. |
src/tokenizer/index.js
Outdated
|
|
||
| readToken_question() { // '?' | ||
| const next = this.input.charCodeAt(this.state.pos + 1); | ||
| if (next === 46) { // 46 = question '.' |
There was a problem hiding this comment.
Comment is confusing; just 46 = '.' should work.
There was a problem hiding this comment.
Or just // '.' like the other parts of the code
src/parser/expression.js
Outdated
| node.callee = this.parseNoCallExpr(); | ||
| return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls); | ||
|
|
||
| } else if (this.eat(tt.questionDot)) { |
There was a problem hiding this comment.
Similar to the other PR, we need to add a flag. I added the steps in https://github.com/babel/babylon/blob/master/CONTRIBUTING.md#creating-a-new-plugin-spec-new.
hasPlugin('nullPropagation') or hasPlugin('optionalChaining'), etc. Probably optionalChaining since the repo was changed to that.
There was a problem hiding this comment.
I added the plugin check, updated the corresponding documentation and also added an explicit error message since it's a new syntax.
| @@ -0,0 +1 @@ | |||
| func?.() | |||
There was a problem hiding this comment.
This test doesn't have an expected?
There was a problem hiding this comment.
Doesn't parse yet, need to be implemented (i'm still WIP)
| @@ -0,0 +1 @@ | |||
| new C?.() | |||
There was a problem hiding this comment.
This test doesn't have an expected?
There was a problem hiding this comment.
Doesn't parse yet, need to be implemented (i'm still WIP)
There was a problem hiding this comment.
Nice work!
- add
hasPluginchecks where necessary - add 2 missing expected checks (estree/estree#146, what do we do with new and function calls?)
- Can we have some more tests? Like at least one to show that you can have
a?.b?.c(multiple chaining in one member expression - Also that
foo ? .3 : 0is parsed as ternary and not optional chaining even while thehasPlugincheck is true - Are there other early errors/tests? Like what if you try to do
a.b?.cora?.b.c.?.d, etc
cc @gisenberg for review, also @kristofdegrave, @rattrayalex
|
When I experimented with this about three years ago, I also went the route to make MemberExpressions optional. Seems like that's the way to go! :) |
|
I believe this breaks valid syntax. The expression |
|
@kerhong Yep, I mentioned this in my review and it's also in https://github.com/tc39/proposal-optional-chaining#notes. |
| base.name === "async" && | ||
| !this.canInsertSemicolon(); | ||
|
|
||
| node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); |
Codecov Report
@@ Coverage Diff @@
## master #545 +/- ##
==========================================
- Coverage 98.14% 98.14% -0.01%
==========================================
Files 22 22
Lines 3620 3663 +43
Branches 1012 1023 +11
==========================================
+ Hits 3553 3595 +42
- Misses 24 25 +1
Partials 43 43
Continue to review full report at Codecov.
|
|
Opened a PR to complete the work, so hopefully this'll land soon. Does anyone know why |
Finish optionalChaining plugin
|
Thanks for the PR @jridgewell, that's a nice job.
Maybe because of the syntax? |
|
cc @yungsters @zertosh if you want to review. Will check again tomorrow |
gisenberg
left a comment
There was a problem hiding this comment.
Looks good to me, great work! Thanks for your contribution!
| this.next(); | ||
|
|
||
| const node = this.startNodeAt(startPos, startLoc); | ||
|
|
There was a problem hiding this comment.
Could probably just move node.optional = true; to here since it's in all the if conditionals?
There was a problem hiding this comment.
I was trying to keep the property ordering consistent between optional and non-optional branches. See the #parseNew as well.
ljharb
left a comment
There was a problem hiding this comment.
Seems legit; I think the transform is where I'll have more opinions than the parsing :-)
| readToken_question() { // '?' | ||
| const next = this.input.charCodeAt(this.state.pos + 1); | ||
| const next2 = this.input.charCodeAt(this.state.pos + 2); | ||
| if (next === 46 && !(next2 >= 48 && next2 <= 57)) { // '.' not followed by a number |
There was a problem hiding this comment.
what are these magic numbers? could these be stored in named variables, so it's clear what "46" means?
There was a problem hiding this comment.
These are char codes. I'll add some contants to improve readability, thanks.
|
🎉 🎉 |
|
Nice work everyone! Really appreciating the reviews! Babel transform PR is |
|
Actually, the transform PR is babel/babel#5813. |
Based on the spec here.
AST change:
interface MemberExpression <: Expression, Pattern { type: "MemberExpression"; object: Expression | Super; property: Expression; computed: boolean; + optional: boolean | null; } interface CallExpression <: Expression { type: "CallExpression"; callee: Expression | Super | Import; arguments: [ Expression | SpreadElement ]; + optional: boolean | null; } interface NewExpression <: CallExpression { type: "NewExpression"; + optional: boolean | null; }Edit:
TODO
a?.b.c.?.d? (skipping ?. in between)