From b902fe6c7bc5fbc64cdca624a62a0648e871ca51 Mon Sep 17 00:00:00 2001 From: Kristof Degrave Date: Sat, 11 Feb 2017 16:44:28 +0100 Subject: [PATCH] ?. && ?[ as member access with nullPropagation. 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. --- ast/spec.md | 3 +- src/parser/expression.js | 16 + src/tokenizer/index.js | 18 +- src/tokenizer/types.js | 2 + .../experimental/uncategorised/63/actual.js | 5 + .../uncategorised/63/expected.json | 570 ++++++++++++++++++ 6 files changed, 612 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/experimental/uncategorised/63/actual.js create mode 100644 test/fixtures/experimental/uncategorised/63/expected.json 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