Skip to content
This repository has been archived by the owner on May 19, 2018. It is now read-only.

Optional Chaining: Stage 1 plugin #545

Merged
merged 17 commits into from
Jun 5, 2017
Merged

Optional Chaining: Stage 1 plugin #545

merged 17 commits into from
Jun 5, 2017

Conversation

xtuc
Copy link
Member

@xtuc xtuc commented May 28, 2017

EDIT by hzoo: Check our spec-new label to follow newly implemented proposals!

Q A
Bug fix? n
Breaking change? n
New feature? y
Deprecations? n
Spec compliancy? y
Tests added/pass? y/n
Fixed tickets #328
License MIT

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

  • Add test for a?.b.c.?.d? (skipping ?. in between)

Kristof Degrave and others added 6 commits February 11, 2017 16:44
…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.
@xtuc xtuc changed the title Feat optional chaining Null Propagation Operator May 28, 2017
@xtuc xtuc mentioned this pull request May 28, 2017
2 tasks
Copy link
Member

@Jessidhia Jessidhia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indicates

@@ -391,6 +391,18 @@ export default class Tokenizer {
return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1);
}

readToken_question() { // '?'
const next = this.input.charCodeAt(this.state.pos + 1);
if (next === 46) { // 46 = question '.'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is confusing; just 46 = '.' should work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or just // '.' like the other parts of the code

@@ -301,6 +301,24 @@ export default class ExpressionParser extends LValParser {
node.object = base;
node.callee = this.parseNoCallExpr();
return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls);

} else if (this.eat(tt.questionDot)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?.()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't have an expected?

Copy link
Member Author

@xtuc xtuc May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't parse yet, need to be implemented (i'm still WIP)

@@ -0,0 +1 @@
new C?.()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't have an expected?

Copy link
Member Author

@xtuc xtuc May 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't parse yet, need to be implemented (i'm still WIP)

Copy link
Member

@hzoo hzoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

  • add hasPlugin checks where necessary
  • add 2 missing expected checks (Optional Chaining Operator 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 : 0 is parsed as ternary and not optional chaining even while the hasPlugin check is true
  • Are there other early errors/tests? Like what if you try to do a.b?.c or a?.b.c.?.d, etc

cc @gisenberg for review, also @kristofdegrave, @rattrayalex

@hzoo hzoo mentioned this pull request May 30, 2017
@cpojer
Copy link
Member

cpojer commented May 30, 2017

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! :)

@hzoo hzoo changed the title Null Propagation Operator Optional Chaining: Stage 1 plugin May 30, 2017
@kerhong
Copy link

kerhong commented May 30, 2017

I believe this breaks valid syntax. The expression foo?.1:bar generates syntax error (Unexpected token), it should (and currently is) interpreted as foo ? 0.1 : bar.

@hzoo
Copy link
Member

hzoo commented May 30, 2017

@kerhong Yep, I mentioned this in my review and it's also in https://github.com/tc39/proposal-optional-chaining#notes.

@hzoo hzoo added the PR: WIP label May 30, 2017
base.name === "async" &&
!this.canInsertSemicolon();

node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no callee

@codecov
Copy link

codecov bot commented Jun 3, 2017

Codecov Report

Merging #545 into master will decrease coverage by <.01%.
The diff coverage is 97.56%.

Impacted file tree graph

@@            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
Flag Coverage Δ
#babel 79.66% <19.51%> (-0.79%) ⬇️
#babylon 96.83% <97.56%> (+0.01%) ⬆️
Impacted Files Coverage Δ
src/tokenizer/types.js 100% <ø> (ø) ⬆️
src/tokenizer/index.js 98.44% <100%> (+0.02%) ⬆️
src/parser/expression.js 96.88% <96.77%> (-0.01%) ⬇️
src/plugins/flow.js 98.39% <0%> (ø) ⬆️
src/plugins/estree.js 99.19% <0%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 324e2f0...4c8f4a2. Read the comment docs.

@jridgewell
Copy link
Member

jridgewell commented Jun 3, 2017

Opened a PR to complete the work, so hopefully this'll land soon.

Does anyone know why .? wasn't considered? As far as I can see, it fixes the foo?.3:0 edge case without introducing any others (there's a slight 1.?1:0 one, but that could be easily handled). I guess it really doesn't matter since you can't have a foo.3 anyways (numeric member expressions have to be computed).

Finish optionalChaining plugin
@xtuc
Copy link
Member Author

xtuc commented Jun 3, 2017

Thanks for the PR @jridgewell, that's a nice job.

Does anyone know why .? wasn't considered?

Maybe because of the syntax? a.?b vs a?.b

@hzoo hzoo requested a review from gisenberg June 3, 2017 14:10
@hzoo
Copy link
Member

hzoo commented Jun 5, 2017

cc @yungsters @zertosh if you want to review.

Will check again tomorrow

Copy link
Member

@gisenberg gisenberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, great work! Thanks for your contribution!

this.next();

const node = this.startNodeAt(startPos, startLoc);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably just move node.optional = true; to here since it's in all the if conditionals?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to keep the property ordering consistent between optional and non-optional branches. See the #parseNew as well.

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these magic numbers? could these be stored in named variables, so it's clear what "46" means?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are char codes. I'll add some contants to improve readability, thanks.

@xtuc xtuc merged commit e064bb9 into babel:master Jun 5, 2017
@xtuc xtuc deleted the feat-optional-chaining branch June 5, 2017 21:13
@davidyaha
Copy link

🎉 🎉

@hzoo
Copy link
Member

hzoo commented Jun 5, 2017

Nice work everyone! Really appreciating the reviews!

Babel transform PR is babel/babel#5786 (please check that out next)

@jridgewell
Copy link
Member

Actually, the transform PR is babel/babel#5813.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants