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

Commit

Permalink
Merge pull request #545 from xtuc/feat-optional-chaining
Browse files Browse the repository at this point in the history
Optional Chaining: Stage 1 plugin
  • Loading branch information
xtuc authored Jun 5, 2017
2 parents 775dcfa + 4c8f4a2 commit e064bb9
Show file tree
Hide file tree
Showing 25 changed files with 2,134 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ require("babylon").parse("code", {
- `functionSent`
- `dynamicImport` ([proposal](https://github.com/tc39/proposal-dynamic-import))
- `numericSeparator` ([proposal](https://github.com/samuelgoto/proposal-numeric-separator))
- `optionalChaining` ([proposal](https://github.com/tc39/proposal-optional-chaining))
- `importMeta` ([proposal](https://github.com/tc39/proposal-import-meta))

### FAQ
Expand Down
5 changes: 4 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 Expand Up @@ -896,6 +897,7 @@ interface CallExpression <: Expression {
type: "CallExpression";
callee: Expression | Super | Import;
arguments: [ Expression | SpreadElement ];
optional: boolean | null;
}
```

Expand All @@ -906,6 +908,7 @@ A function or method call expression.
```js
interface NewExpression <: CallExpression {
type: "NewExpression";
optional: boolean | null;
}
```

Expand Down
42 changes: 42 additions & 0 deletions src/parser/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,44 @@ 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.match(tt.questionDot)) {
if (!this.hasPlugin("optionalChaining")) {
this.raise(startPos, "You can only use optional-chaining when the 'optionalChaining' plugin is enabled.");
}

if (noCalls && this.lookahead().type == tt.parenL) {
return base;
}
this.next();

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

if (this.eat(tt.bracketL)) {
node.object = base;
node.property = this.parseExpression();
node.computed = true;
node.optional = true;
this.expect(tt.bracketR);
base = this.finishNode(node, "MemberExpression");
} else if (this.eat(tt.parenL)) {
const possibleAsync = this.state.potentialArrowAt === base.start &&
base.type === "Identifier" &&
base.name === "async" &&
!this.canInsertSemicolon();

node.callee = base;
node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync);
node.optional = true;

base = this.finishNode(node, "CallExpression");
} else {
node.object = base;
node.property = this.parseIdentifier(true);
node.computed = false;
node.optional = true;
base = this.finishNode(node, "MemberExpression");
}
} else if (this.eat(tt.dot)) {
const node = this.startNodeAt(startPos, startLoc);
node.object = base;
Expand Down Expand Up @@ -736,13 +774,17 @@ export default class ExpressionParser extends LValParser {
}

node.callee = this.parseNoCallExpr();
const optional = this.eat(tt.questionDot);

if (this.eat(tt.parenL)) {
node.arguments = this.parseExprList(tt.parenR);
this.toReferencedList(node.arguments);
} else {
node.arguments = [];
}
if (optional) {
node.optional = true;
}

return this.finishNode(node, "NewExpression");
}
Expand Down
16 changes: 14 additions & 2 deletions src/tokenizer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,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);
const next2 = this.input.charCodeAt(this.state.pos + 2);
if (next === 46 && !(next2 >= 48 && next2 <= 57)) { // '.' not followed by a number
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 @@ -469,7 +481,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 @@ -917,7 +929,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,9 @@
new C?.()

new C?.(a, b)

new B?.C?.()

new B?.C?.(a, b)

new B?.C
Loading

0 comments on commit e064bb9

Please sign in to comment.