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
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ast/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,10 +859,11 @@ interface MemberExpression <: Expression, Pattern {
object: Expression | Super;
property: Expression;
computed: boolean;
optional: boolean | null;
}
```

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 indicates 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.

### BindExpression

Expand Down
18 changes: 18 additions & 0 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.

if (this.eat(tt.bracketL)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
node.optional = true;
node.property = this.parseExpression();
node.computed = true;
this.expect(tt.bracketR);
base = this.finishNode(node, "MemberExpression");
} else {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
node.property = this.parseIdentifier(true);
node.optional = true;
node.computed = false;
base = this.finishNode(node, "MemberExpression");
}
} else if (this.eat(tt.dot)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
Expand Down
16 changes: 14 additions & 2 deletions src/tokenizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,18 @@ export default class Tokenizer extends LocationParser {
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

this.state.pos += 2;
return this.finishToken(tt.questionDot);
}
else {
++this.state.pos;
return this.finishToken(tt.question);
}
}

getTokenFromCode(code: number): void {
switch (code) {

Expand Down Expand Up @@ -463,7 +475,7 @@ export default class Tokenizer extends LocationParser {
return this.finishToken(tt.colon);
}

case 63: ++this.state.pos; return this.finishToken(tt.question);
case 63: return this.readToken_question();
case 64: ++this.state.pos; return this.finishToken(tt.at);

case 96: // '`'
Expand Down Expand Up @@ -908,7 +920,7 @@ export default class Tokenizer extends LocationParser {
const type = this.state.type;
let update;

if (type.keyword && prevType === tt.dot) {
if (type.keyword && (prevType === tt.dot || prevType === tt.questionDot)) {
this.state.exprAllowed = false;
} else if (update = type.updateContext) {
update.call(this, prevType);
Expand Down
1 change: 1 addition & 0 deletions src/tokenizer/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const types: { [name: string]: TokenType } = {
doubleColon: new TokenType("::", { beforeExpr }),
dot: new TokenType("."),
question: new TokenType("?", { beforeExpr }),
questionDot: new TokenType("?."),
arrow: new TokenType("=>", { beforeExpr }),
template: new TokenType("template"),
ellipsis: new TokenType("...", { beforeExpr }),
Expand Down
Original file line number Diff line number Diff line change
@@ -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)

Original file line number Diff line number Diff line change
@@ -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)

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
obj?.[expr]
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"type": "File",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 11
}
},
"program": {
"type": "Program",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 11
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 11
}
},
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 11,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 11
}
},
"object": {
"type": "Identifier",
"start": 0,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 3
},
"identifierName": "obj"
},
"name": "obj"
},
"optional": true,
"property": {
"type": "Identifier",
"start": 6,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 10
},
"identifierName": "expr"
},
"name": "expr"
},
"computed": true
}
}
],
"directives": []
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo?.bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"type": "File",
"start": 0,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 8
}
},
"program": {
"type": "Program",
"start": 0,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 8
}
},
"sourceType": "script",
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 8
}
},
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 8
}
},
"object": {
"type": "Identifier",
"start": 0,
"end": 3,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 3
},
"identifierName": "foo"
},
"name": "foo"
},
"property": {
"type": "Identifier",
"start": 5,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 5
},
"end": {
"line": 1,
"column": 8
},
"identifierName": "bar"
},
"name": "bar"
},
"optional": true,
"computed": false
}
}
],
"directives": []
}
}