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 6 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 @@ -845,10 +845,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 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


### BindExpression

Expand Down
17 changes: 17 additions & 0 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,23 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
node.object = base;
node.callee = this.parseNoCallExpr();
return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls);
} else if (this.eat(tt.questionDot)) {
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 @@ -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

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

getTokenFromCode(code) {
switch (code) {
// The interpretation of a dot depends on whether it is followed
Expand Down Expand Up @@ -425,7 +437,7 @@ export default class Tokenizer {
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 @@ -844,7 +856,7 @@ export default class Tokenizer {
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 @@ -75,6 +75,7 @@ export const types = {
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": []
}
}