diff --git a/ast/spec.md b/ast/spec.md index 346b7b95aa..f75e121ee9 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -857,10 +857,11 @@ interface MemberExpression <: Expression, Pattern { object: Expression | Super; property: Expression; computed: boolean; + nullPropagation: boolean; } ``` -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 `nullPropagation` 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. ### BindExpression diff --git a/src/parser/expression.js b/src/parser/expression.js index 2919c6f50d..a1d709f06a 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -284,17 +284,33 @@ 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)) { + let node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.property = this.parseIdentifier(true); + node.computed = false; + node.nullPropagation = true; + base = this.finishNode(node, "MemberExpression"); } else if (this.eat(tt.dot)) { const node = this.startNodeAt(startPos, startLoc); node.object = base; node.property = this.parseIdentifier(true); node.computed = false; + node.nullPropagation = false; base = this.finishNode(node, "MemberExpression"); + } else if (this.eat(tt.questionBracketL)) { + let node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.property = this.parseExpression(); + node.computed = true; + node.nullPropagation = true; + this.expect(tt.bracketR); } else if (this.eat(tt.bracketL)) { const node = this.startNodeAt(startPos, startLoc); node.object = base; node.property = this.parseExpression(); node.computed = true; + node.nullPropagation = false; this.expect(tt.bracketR); base = this.finishNode(node, "MemberExpression"); } else if (!noCalls && this.match(tt.parenL)) { diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index 4c35054c2b..9809c6820d 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -391,6 +391,22 @@ export default class Tokenizer { return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1); } + readToken_question() { // '?' + let next = this.input.charCodeAt(this.state.pos + 1); + if(next === 46){ // 46 = question '.' + this.state.pos += 2; + return this.finishToken(tt.questionDot); + } + else if(next === 91){ // 91 = question '[' + this.state.pos += 2; + return this.finishToken(tt.questionBracketL); + } + else { + ++this.state.pos; + return this.finishToken(tt.question); + } + } + getTokenFromCode(code) { switch (code) { // The interpretation of a dot depends on whether it is followed @@ -425,7 +441,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: // '`' diff --git a/src/tokenizer/types.js b/src/tokenizer/types.js index b82dfe0902..77a77e40d4 100644 --- a/src/tokenizer/types.js +++ b/src/tokenizer/types.js @@ -75,6 +75,8 @@ export const types = { doubleColon: new TokenType("::", { beforeExpr }), dot: new TokenType("."), question: new TokenType("?", { beforeExpr }), + questionBracketL: new TokenType("?[", { beforeExpr, startsExpr }), + questionBracketL: new TokenType("?."), arrow: new TokenType("=>", { beforeExpr }), template: new TokenType("template"), ellipsis: new TokenType("...", { beforeExpr }), diff --git a/test/fixtures/experimental/uncategorised/63/actual.js b/test/fixtures/experimental/uncategorised/63/actual.js new file mode 100644 index 0000000000..85718cbd72 --- /dev/null +++ b/test/fixtures/experimental/uncategorised/63/actual.js @@ -0,0 +1,5 @@ +o?.x?.y + +o?.x ? o.x.z?.w : o.y?.z?.w + +o?[0]?[1]?.x \ No newline at end of file diff --git a/test/fixtures/experimental/uncategorised/63/expected.json b/test/fixtures/experimental/uncategorised/63/expected.json new file mode 100644 index 0000000000..bead29a860 --- /dev/null +++ b/test/fixtures/experimental/uncategorised/63/expected.json @@ -0,0 +1,570 @@ +{ + "type": "File", + "start": 0, + "end": 50, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 50, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "expression": { + "type": "MemberExpression", + "start": 0, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "object": { + "type": "MemberExpression", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "object": { + "type": "Identifier", + "start": 0, + "end": 1, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + }, + "name": "o", + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 3, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "name": "x" + }, + "computed": false, + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "name": "y" + }, + "computed": false + } + }, + { + "type": "ExpressionStatement", + "start": 9, + "end": 36, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 27 + } + }, + "expression": { + "type": "ConditionalExpression", + "start": 9, + "end": 36, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 27 + } + }, + "test": { + "type": "MemberExpression", + "start": 9, + "end": 13, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 4 + } + }, + "object": { + "type": "Identifier", + "start": 9, + "end": 10, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + }, + "name": "o", + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 12, + "end": 13, + "loc": { + "start": { + "line": 3, + "column": 3 + }, + "end": { + "line": 3, + "column": 4 + } + }, + "name": "x" + }, + "computed": false + }, + "consequent": { + "type": "MemberExpression", + "start": 16, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 15 + } + }, + "object": { + "type": "MemberExpression", + "start": 16, + "end": 21, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "object": { + "type": "MemberExpression", + "start": 16, + "end": 19, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 10 + } + }, + "object": { + "type": "Identifier", + "start": 16, + "end": 17, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 8 + } + }, + "name": "o" + }, + "property": { + "type": "Identifier", + "start": 18, + "end": 19, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 10 + } + }, + "name": "x" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 20, + "end": 21, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "name": "z" + }, + "computed": false, + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 23, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 15 + } + }, + "name": "w" + }, + "computed": false + }, + "alternate": { + "type": "MemberExpression", + "start": 27, + "end": 36, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 27 + } + }, + "object": { + "type": "MemberExpression", + "start": 27, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 24 + } + }, + "object": { + "type": "MemberExpression", + "start": 27, + "end": 30, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "object": { + "type": "Identifier", + "start": 27, + "end": 28, + "loc": { + "start": { + "line": 3, + "column": 18 + }, + "end": { + "line": 3, + "column": 19 + } + }, + "name": "o" + }, + "property": { + "type": "Identifier", + "start": 29, + "end": 30, + "loc": { + "start": { + "line": 3, + "column": 20 + }, + "end": { + "line": 3, + "column": 21 + } + }, + "name": "y" + }, + "computed": false, + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 32, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 23 + }, + "end": { + "line": 3, + "column": 24 + } + }, + "name": "z" + }, + "computed": false, + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 35, + "end": 36, + "loc": { + "start": { + "line": 3, + "column": 26 + }, + "end": { + "line": 3, + "column": 27 + } + }, + "name": "w" + }, + "computed": false + } + } + }, + { + "type": "ExpressionStatement", + "start": 38, + "end": 50, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "expression": { + "type": "MemberExpression", + "start": 38, + "end": 50, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "object": { + "type": "MemberExpression", + "start": 38, + "end": 47, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 9 + } + }, + "object": { + "type": "MemberExpression", + "start": 38, + "end": 43, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 5 + } + }, + "object": { + "type": "Identifier", + "start": 38, + "end": 39, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 1 + } + }, + "name": "o", + "existentialOperator": true + }, + "property": { + "type": "Literal", + "start": 41, + "end": 42, + "loc": { + "start": { + "line": 5, + "column": 3 + }, + "end": { + "line": 5, + "column": 4 + } + }, + "value": 0, + "rawValue": 0, + "raw": "0" + }, + "computed": true, + "existentialOperator": true + }, + "property": { + "type": "Literal", + "start": 45, + "end": 46, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + } + }, + "value": 1, + "rawValue": 1, + "raw": "1" + }, + "computed": true, + "existentialOperator": true + }, + "property": { + "type": "Identifier", + "start": 49, + "end": 50, + "loc": { + "start": { + "line": 5, + "column": 11 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "name": "x" + }, + "computed": false + } + } + ] + } +} \ No newline at end of file