From b902fe6c7bc5fbc64cdca624a62a0648e871ca51 Mon Sep 17 00:00:00 2001 From: Kristof Degrave Date: Sat, 11 Feb 2017 16:44:28 +0100 Subject: [PATCH 01/13] ?. && ?[ 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 From 1eaf01661b2af969463e9726b445de3ca8bed018 Mon Sep 17 00:00:00 2001 From: Kristof Degrave Date: Sat, 11 Feb 2017 21:09:27 +0100 Subject: [PATCH 02/13] babel/babylon#328 babel/babylon#205 ?. as nullPropagation. For now it only works for member access. (.? or ?.[) 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 | 2 +- src/parser/expression.js | 32 ++++---- src/tokenizer/index.js | 26 +++---- src/tokenizer/types.js | 3 +- .../experimental/uncategorised/63/actual.js | 2 +- .../uncategorised/63/expected.json | 78 +++++++++---------- 6 files changed, 68 insertions(+), 75 deletions(-) diff --git a/ast/spec.md b/ast/spec.md index f75e121ee9..5bc39185c8 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -857,7 +857,7 @@ interface MemberExpression <: Expression, Pattern { object: Expression | Super; property: Expression; computed: boolean; - nullPropagation: boolean; + nullPropagation: boolean | null; } ``` diff --git a/src/parser/expression.js b/src/parser/expression.js index a1d709f06a..24b35c4ee3 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -284,27 +284,29 @@ 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.questionDot)) { + base.nullPropagation = true; + if (this.eat(tt.bracketL)) { + const node = this.startNodeAt(startPos, startLoc); + node.object = base; + 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.computed = false; + 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; diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index 9809c6820d..5a2326c296 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -392,20 +392,16 @@ export default class Tokenizer { } 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); - } - } + const next = this.input.charCodeAt(this.state.pos + 1); + if (next === 46) { // 46 = question '.' + this.state.pos += 2; + return this.finishToken(tt.questionDot); + } + else { + ++this.state.pos; + return this.finishToken(tt.question); + } + } getTokenFromCode(code) { switch (code) { @@ -825,7 +821,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); diff --git a/src/tokenizer/types.js b/src/tokenizer/types.js index 77a77e40d4..e7f32619fc 100644 --- a/src/tokenizer/types.js +++ b/src/tokenizer/types.js @@ -75,8 +75,7 @@ export const types = { doubleColon: new TokenType("::", { beforeExpr }), dot: new TokenType("."), question: new TokenType("?", { beforeExpr }), - questionBracketL: new TokenType("?[", { beforeExpr, startsExpr }), - questionBracketL: new TokenType("?."), + questionDot: 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 index 85718cbd72..eeb713af30 100644 --- a/test/fixtures/experimental/uncategorised/63/actual.js +++ b/test/fixtures/experimental/uncategorised/63/actual.js @@ -2,4 +2,4 @@ o?.x?.y o?.x ? o.x.z?.w : o.y?.z?.w -o?[0]?[1]?.x \ No newline at end of file +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 index bead29a860..7f36efbc14 100644 --- a/test/fixtures/experimental/uncategorised/63/expected.json +++ b/test/fixtures/experimental/uncategorised/63/expected.json @@ -1,7 +1,7 @@ { "type": "File", "start": 0, - "end": 50, + "end": 54, "loc": { "start": { "line": 1, @@ -9,13 +9,13 @@ }, "end": { "line": 5, - "column": 12 + "column": 16 } }, "program": { "type": "Program", "start": 0, - "end": 50, + "end": 54, "loc": { "start": { "line": 1, @@ -23,7 +23,7 @@ }, "end": { "line": 5, - "column": 12 + "column": 16 } }, "sourceType": "script", @@ -85,7 +85,7 @@ } }, "name": "o", - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -104,7 +104,7 @@ "name": "x" }, "computed": false, - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -182,7 +182,7 @@ } }, "name": "o", - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -295,7 +295,7 @@ "name": "z" }, "computed": false, - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -390,7 +390,7 @@ "name": "y" }, "computed": false, - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -409,7 +409,7 @@ "name": "z" }, "computed": false, - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", @@ -434,7 +434,7 @@ { "type": "ExpressionStatement", "start": 38, - "end": 50, + "end": 54, "loc": { "start": { "line": 5, @@ -442,13 +442,13 @@ }, "end": { "line": 5, - "column": 12 + "column": 16 } }, "expression": { "type": "MemberExpression", "start": 38, - "end": 50, + "end": 54, "loc": { "start": { "line": 5, @@ -456,13 +456,13 @@ }, "end": { "line": 5, - "column": 12 + "column": 16 } }, "object": { "type": "MemberExpression", "start": 38, - "end": 47, + "end": 51, "loc": { "start": { "line": 5, @@ -470,13 +470,13 @@ }, "end": { "line": 5, - "column": 9 + "column": 13 } }, "object": { "type": "MemberExpression", "start": 38, - "end": 43, + "end": 44, "loc": { "start": { "line": 5, @@ -484,7 +484,7 @@ }, "end": { "line": 5, - "column": 5 + "column": 6 } }, "object": { @@ -502,62 +502,58 @@ } }, "name": "o", - "existentialOperator": true + "nullPropagation": true }, "property": { - "type": "Literal", - "start": 41, - "end": 42, + "type": "NumericLiteral", + "start": 42, + "end": 43, "loc": { "start": { "line": 5, - "column": 3 + "column": 4 }, "end": { "line": 5, - "column": 4 + "column": 5 } }, - "value": 0, - "rawValue": 0, - "raw": "0" + "value": 0 }, "computed": true, - "existentialOperator": true + "nullPropagation": true }, "property": { - "type": "Literal", - "start": 45, - "end": 46, + "type": "StringLiteral", + "start": 47, + "end": 50, "loc": { "start": { "line": 5, - "column": 7 + "column": 9 }, "end": { "line": 5, - "column": 8 + "column": 12 } }, - "value": 1, - "rawValue": 1, - "raw": "1" + "value": "1" }, "computed": true, - "existentialOperator": true + "nullPropagation": true }, "property": { "type": "Identifier", - "start": 49, - "end": 50, + "start": 53, + "end": 54, "loc": { "start": { "line": 5, - "column": 11 + "column": 15 }, "end": { "line": 5, - "column": 12 + "column": 16 } }, "name": "x" From 7b51979152be20fe472a5fada7308836f0911cac Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Sat, 15 Apr 2017 18:19:43 +0200 Subject: [PATCH 03/13] feat: add optional MemberExpression --- src/parser/expression.js | 8 ++ .../optional-chaining/member-access/actual.js | 1 + .../member-access/expected.json | 100 ++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 test/fixtures/es2017/optional-chaining/member-access/actual.js create mode 100644 test/fixtures/es2017/optional-chaining/member-access/expected.json diff --git a/src/parser/expression.js b/src/parser/expression.js index aeb6464f8d..5e5184a875 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -284,6 +284,14 @@ 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.question)) { + const node = this.startNodeAt(startPos, startLoc); + node.object = base; + node.optional = true; + this.next(); + node.property = this.parseIdentifier(true); + node.computed = false; + base = this.finishNode(node, "MemberExpression"); } else if (this.eat(tt.dot)) { const node = this.startNodeAt(startPos, startLoc); node.object = base; diff --git a/test/fixtures/es2017/optional-chaining/member-access/actual.js b/test/fixtures/es2017/optional-chaining/member-access/actual.js new file mode 100644 index 0000000000..6ed5c83b93 --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/member-access/actual.js @@ -0,0 +1 @@ +foo?.bar diff --git a/test/fixtures/es2017/optional-chaining/member-access/expected.json b/test/fixtures/es2017/optional-chaining/member-access/expected.json new file mode 100644 index 0000000000..cb6ced4220 --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/member-access/expected.json @@ -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" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 5, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "bar" + }, + "name": "bar" + }, + "computed": false + } + } + ], + "directives": [] + } +} \ No newline at end of file From b2fdd944fe740a86122776f71d027fc2f1ecefac Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Mon, 17 Apr 2017 13:33:00 +0200 Subject: [PATCH 04/13] feat: add tests --- ast/spec.md | 4 +- src/parser/expression.js | 5 +- .../class-contructor-call/actual.js | 1 + .../optional-chaining/function-call/actual.js | 1 + .../member-access-bracket/actual.js | 1 + .../member-access-bracket/expected.json | 100 ++++ .../member-access/expected.json | 2 +- .../experimental/uncategorised/63/actual.js | 5 - .../uncategorised/63/expected.json | 566 ------------------ 9 files changed, 108 insertions(+), 577 deletions(-) create mode 100644 test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js create mode 100644 test/fixtures/es2017/optional-chaining/function-call/actual.js create mode 100644 test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js create mode 100644 test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json delete mode 100644 test/fixtures/experimental/uncategorised/63/actual.js delete mode 100644 test/fixtures/experimental/uncategorised/63/expected.json diff --git a/ast/spec.md b/ast/spec.md index 72562cb48c..96425d2fa4 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -845,11 +845,11 @@ interface MemberExpression <: Expression, Pattern { object: Expression | Super; property: Expression; computed: boolean; - nullPropagation: boolean | null; + 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`. 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. +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. ### BindExpression diff --git a/src/parser/expression.js b/src/parser/expression.js index e8532ae1a9..8bcb0422cb 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -285,11 +285,10 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { node.callee = this.parseNoCallExpr(); return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls); } else if (this.eat(tt.questionDot)) { - base.optional = true; - 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); @@ -298,6 +297,7 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { 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"); } @@ -312,7 +312,6 @@ pp.parseSubscripts = function (base, startPos, startLoc, noCalls) { node.object = base; node.property = this.parseExpression(); node.computed = true; - delete node.nullPropagation; this.expect(tt.bracketR); base = this.finishNode(node, "MemberExpression"); } else if (!noCalls && this.match(tt.parenL)) { diff --git a/test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js b/test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js new file mode 100644 index 0000000000..8e9b00d27f --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js @@ -0,0 +1 @@ +new C?.() diff --git a/test/fixtures/es2017/optional-chaining/function-call/actual.js b/test/fixtures/es2017/optional-chaining/function-call/actual.js new file mode 100644 index 0000000000..4488735365 --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/function-call/actual.js @@ -0,0 +1 @@ +func?.() diff --git a/test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js b/test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js new file mode 100644 index 0000000000..faf3fc3ac0 --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js @@ -0,0 +1 @@ +obj?.[expr] diff --git a/test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json b/test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json new file mode 100644 index 0000000000..cd6a6d9c88 --- /dev/null +++ b/test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json @@ -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": [] + } +} \ No newline at end of file diff --git a/test/fixtures/es2017/optional-chaining/member-access/expected.json b/test/fixtures/es2017/optional-chaining/member-access/expected.json index cb6ced4220..868d2a3e08 100644 --- a/test/fixtures/es2017/optional-chaining/member-access/expected.json +++ b/test/fixtures/es2017/optional-chaining/member-access/expected.json @@ -73,7 +73,6 @@ }, "name": "foo" }, - "optional": true, "property": { "type": "Identifier", "start": 5, @@ -91,6 +90,7 @@ }, "name": "bar" }, + "optional": true, "computed": false } } diff --git a/test/fixtures/experimental/uncategorised/63/actual.js b/test/fixtures/experimental/uncategorised/63/actual.js deleted file mode 100644 index eeb713af30..0000000000 --- a/test/fixtures/experimental/uncategorised/63/actual.js +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index 7f36efbc14..0000000000 --- a/test/fixtures/experimental/uncategorised/63/expected.json +++ /dev/null @@ -1,566 +0,0 @@ -{ - "type": "File", - "start": 0, - "end": 54, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 5, - "column": 16 - } - }, - "program": { - "type": "Program", - "start": 0, - "end": 54, - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 5, - "column": 16 - } - }, - "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", - "nullPropagation": true - }, - "property": { - "type": "Identifier", - "start": 3, - "end": 4, - "loc": { - "start": { - "line": 1, - "column": 3 - }, - "end": { - "line": 1, - "column": 4 - } - }, - "name": "x" - }, - "computed": false, - "nullPropagation": 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", - "nullPropagation": 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, - "nullPropagation": 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, - "nullPropagation": true - }, - "property": { - "type": "Identifier", - "start": 32, - "end": 33, - "loc": { - "start": { - "line": 3, - "column": 23 - }, - "end": { - "line": 3, - "column": 24 - } - }, - "name": "z" - }, - "computed": false, - "nullPropagation": 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": 54, - "loc": { - "start": { - "line": 5, - "column": 0 - }, - "end": { - "line": 5, - "column": 16 - } - }, - "expression": { - "type": "MemberExpression", - "start": 38, - "end": 54, - "loc": { - "start": { - "line": 5, - "column": 0 - }, - "end": { - "line": 5, - "column": 16 - } - }, - "object": { - "type": "MemberExpression", - "start": 38, - "end": 51, - "loc": { - "start": { - "line": 5, - "column": 0 - }, - "end": { - "line": 5, - "column": 13 - } - }, - "object": { - "type": "MemberExpression", - "start": 38, - "end": 44, - "loc": { - "start": { - "line": 5, - "column": 0 - }, - "end": { - "line": 5, - "column": 6 - } - }, - "object": { - "type": "Identifier", - "start": 38, - "end": 39, - "loc": { - "start": { - "line": 5, - "column": 0 - }, - "end": { - "line": 5, - "column": 1 - } - }, - "name": "o", - "nullPropagation": true - }, - "property": { - "type": "NumericLiteral", - "start": 42, - "end": 43, - "loc": { - "start": { - "line": 5, - "column": 4 - }, - "end": { - "line": 5, - "column": 5 - } - }, - "value": 0 - }, - "computed": true, - "nullPropagation": true - }, - "property": { - "type": "StringLiteral", - "start": 47, - "end": 50, - "loc": { - "start": { - "line": 5, - "column": 9 - }, - "end": { - "line": 5, - "column": 12 - } - }, - "value": "1" - }, - "computed": true, - "nullPropagation": true - }, - "property": { - "type": "Identifier", - "start": 53, - "end": 54, - "loc": { - "start": { - "line": 5, - "column": 15 - }, - "end": { - "line": 5, - "column": 16 - } - }, - "name": "x" - }, - "computed": false - } - } - ] - } -} \ No newline at end of file From bc9edd139f5c5ca6ec9288c25bbd98c84c675041 Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Mon, 17 Apr 2017 13:33:48 +0200 Subject: [PATCH 05/13] fix: move tests in experimental --- .../optional-chaining/class-contructor-call/actual.js | 0 .../optional-chaining/function-call/actual.js | 0 .../optional-chaining/member-access-bracket/actual.js | 0 .../optional-chaining/member-access-bracket/expected.json | 0 .../optional-chaining/member-access/actual.js | 0 .../optional-chaining/member-access/expected.json | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename test/fixtures/{es2017 => experimental}/optional-chaining/class-contructor-call/actual.js (100%) rename test/fixtures/{es2017 => experimental}/optional-chaining/function-call/actual.js (100%) rename test/fixtures/{es2017 => experimental}/optional-chaining/member-access-bracket/actual.js (100%) rename test/fixtures/{es2017 => experimental}/optional-chaining/member-access-bracket/expected.json (100%) rename test/fixtures/{es2017 => experimental}/optional-chaining/member-access/actual.js (100%) rename test/fixtures/{es2017 => experimental}/optional-chaining/member-access/expected.json (100%) diff --git a/test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js similarity index 100% rename from test/fixtures/es2017/optional-chaining/class-contructor-call/actual.js rename to test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js diff --git a/test/fixtures/es2017/optional-chaining/function-call/actual.js b/test/fixtures/experimental/optional-chaining/function-call/actual.js similarity index 100% rename from test/fixtures/es2017/optional-chaining/function-call/actual.js rename to test/fixtures/experimental/optional-chaining/function-call/actual.js diff --git a/test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js b/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js similarity index 100% rename from test/fixtures/es2017/optional-chaining/member-access-bracket/actual.js rename to test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js diff --git a/test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json b/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json similarity index 100% rename from test/fixtures/es2017/optional-chaining/member-access-bracket/expected.json rename to test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json diff --git a/test/fixtures/es2017/optional-chaining/member-access/actual.js b/test/fixtures/experimental/optional-chaining/member-access/actual.js similarity index 100% rename from test/fixtures/es2017/optional-chaining/member-access/actual.js rename to test/fixtures/experimental/optional-chaining/member-access/actual.js diff --git a/test/fixtures/es2017/optional-chaining/member-access/expected.json b/test/fixtures/experimental/optional-chaining/member-access/expected.json similarity index 100% rename from test/fixtures/es2017/optional-chaining/member-access/expected.json rename to test/fixtures/experimental/optional-chaining/member-access/expected.json From 03d89b6307e1eb9af7ae21509067ba433a0a7608 Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Mon, 29 May 2017 18:46:52 +0200 Subject: [PATCH 06/13] docs: fix typo in spec [skip ci] --- ast/spec.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ast/spec.md b/ast/spec.md index 839239353b..1dd5be0282 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -863,7 +863,7 @@ interface MemberExpression <: Expression, Pattern { } ``` -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. +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 From 51bd87baa81e1ef1a43fe91e97ef8bea60cef56c Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Tue, 30 May 2017 18:35:29 +0200 Subject: [PATCH 07/13] feat: use syntax plugin --- README.md | 1 + src/parser/expression.js | 8 ++++++-- .../optional-chaining/class-contructor-call/options.json | 3 +++ .../optional-chaining/function-call/options.json | 3 +++ .../optional-chaining/member-access-bracket/options.json | 3 +++ .../optional-chaining/member-access/options.json | 3 +++ .../optional-chaining/missing-plugin/actual.js | 1 + .../optional-chaining/missing-plugin/options.json | 3 +++ 8 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/experimental/optional-chaining/class-contructor-call/options.json create mode 100644 test/fixtures/experimental/optional-chaining/function-call/options.json create mode 100644 test/fixtures/experimental/optional-chaining/member-access-bracket/options.json create mode 100644 test/fixtures/experimental/optional-chaining/member-access/options.json create mode 100644 test/fixtures/experimental/optional-chaining/missing-plugin/actual.js create mode 100644 test/fixtures/experimental/optional-chaining/missing-plugin/options.json diff --git a/README.md b/README.md index be6f771feb..c2b511d2e8 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,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)) ### FAQ diff --git a/src/parser/expression.js b/src/parser/expression.js index a29b84a4d2..0ab2c501cd 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -303,8 +303,13 @@ export default class ExpressionParser extends LValParser { return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls); } else if (this.eat(tt.questionDot)) { + const node = this.startNodeAt(startPos, startLoc); + + if (!this.hasPlugin("optionalChaining")) { + this.raise(node.start, "You can only use optional-chaining when the 'optionalChaining' plugin is enabled."); + } + if (this.eat(tt.bracketL)) { - const node = this.startNodeAt(startPos, startLoc); node.object = base; node.optional = true; node.property = this.parseExpression(); @@ -312,7 +317,6 @@ export default class ExpressionParser extends LValParser { 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; diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/options.json b/test/fixtures/experimental/optional-chaining/class-contructor-call/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +} diff --git a/test/fixtures/experimental/optional-chaining/function-call/options.json b/test/fixtures/experimental/optional-chaining/function-call/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/function-call/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +} diff --git a/test/fixtures/experimental/optional-chaining/member-access-bracket/options.json b/test/fixtures/experimental/optional-chaining/member-access-bracket/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access-bracket/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +} diff --git a/test/fixtures/experimental/optional-chaining/member-access/options.json b/test/fixtures/experimental/optional-chaining/member-access/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +} diff --git a/test/fixtures/experimental/optional-chaining/missing-plugin/actual.js b/test/fixtures/experimental/optional-chaining/missing-plugin/actual.js new file mode 100644 index 0000000000..2807546113 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/missing-plugin/actual.js @@ -0,0 +1 @@ +a?.b diff --git a/test/fixtures/experimental/optional-chaining/missing-plugin/options.json b/test/fixtures/experimental/optional-chaining/missing-plugin/options.json new file mode 100644 index 0000000000..9aa9db14c9 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/missing-plugin/options.json @@ -0,0 +1,3 @@ +{ + "throws": "You can only use optional-chaining when the 'optionalChaining' plugin is enabled. (1:0)" +} From 26096d6a3d5526383869e47294a3e42f9c2d2d68 Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Tue, 30 May 2017 18:38:50 +0200 Subject: [PATCH 08/13] style: [skip ci] updated comment --- src/tokenizer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index bb7c414922..acc145de03 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -422,7 +422,7 @@ export default class Tokenizer extends LocationParser { readToken_question() { // '?' const next = this.input.charCodeAt(this.state.pos + 1); - if (next === 46) { // 46 = question '.' + if (next === 46) { // '.' this.state.pos += 2; return this.finishToken(tt.questionDot); } From 9bcd85acf3013cdadadc7d6e6a60795926723c56 Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Tue, 30 May 2017 20:12:43 +0200 Subject: [PATCH 09/13] feat: CallExpression support --- ast/spec.md | 1 + src/parser/expression.js | 10 ++ .../optional-chaining/function-call/actual.js | 2 + .../function-call/expected.json | 133 ++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 test/fixtures/experimental/optional-chaining/function-call/expected.json diff --git a/ast/spec.md b/ast/spec.md index 1dd5be0282..ce24b1e5df 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -897,6 +897,7 @@ interface CallExpression <: Expression { type: "CallExpression"; callee: Expression | Super | Import; arguments: [ Expression | SpreadElement ]; + optional: boolean | null; } ``` diff --git a/src/parser/expression.js b/src/parser/expression.js index 0ab2c501cd..30e459c3a0 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -316,6 +316,16 @@ export default class ExpressionParser extends LValParser { node.computed = true; this.expect(tt.bracketR); base = this.finishNode(node, "MemberExpression"); + } if (this.eat(tt.parenL)) { + const possibleAsync = this.state.potentialArrowAt === base.start && + base.type === "Identifier" && + base.name === "async" && + !this.canInsertSemicolon(); + + node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); + node.optional = true; + + base = this.finishNode(node, "CallExpression"); } else { node.object = base; node.property = this.parseIdentifier(true); diff --git a/test/fixtures/experimental/optional-chaining/function-call/actual.js b/test/fixtures/experimental/optional-chaining/function-call/actual.js index 4488735365..7a5a8c001b 100644 --- a/test/fixtures/experimental/optional-chaining/function-call/actual.js +++ b/test/fixtures/experimental/optional-chaining/function-call/actual.js @@ -1 +1,3 @@ func?.() + +func?.(a, b) diff --git a/test/fixtures/experimental/optional-chaining/function-call/expected.json b/test/fixtures/experimental/optional-chaining/function-call/expected.json new file mode 100644 index 0000000000..60248b0e79 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/function-call/expected.json @@ -0,0 +1,133 @@ +{ + "type": "File", + "start": 0, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 22, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "expression": { + "type": "CallExpression", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "arguments": [], + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 10, + "end": 22, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "expression": { + "type": "CallExpression", + "start": 10, + "end": 22, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "arguments": [ + { + "type": "Identifier", + "start": 17, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 8 + }, + "identifierName": "a" + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 20, + "end": 21, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 11 + }, + "identifierName": "b" + }, + "name": "b" + } + ], + "optional": true + } + } + ], + "directives": [] + } +} \ No newline at end of file From b0386005c818d308ad0980a4a48042697a5f60ec Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Tue, 30 May 2017 20:14:19 +0200 Subject: [PATCH 10/13] docs: NewExpression spec --- ast/spec.md | 1 + .../class-contructor-call/actual.js | 2 + .../class-contructor-call/expected.json | 132 ++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json diff --git a/ast/spec.md b/ast/spec.md index ce24b1e5df..b5d6517184 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -908,6 +908,7 @@ A function or method call expression. ```js interface NewExpression <: CallExpression { type: "NewExpression"; + optional: boolean | null; } ``` diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js index 8e9b00d27f..e08331157e 100644 --- a/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js @@ -1 +1,3 @@ new C?.() + +new C?.(a, b) diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json b/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json new file mode 100644 index 0000000000..38e9ba308e --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json @@ -0,0 +1,132 @@ +{ + "type": "File", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "expression": { + "type": "NewExpression", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "callee": { + "type": "Identifier", + "start": 4, + "end": 5, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 5 + }, + "identifierName": "C" + }, + "name": "C" + }, + "arguments": [], + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 11, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "expression": { + "type": "NewExpression", + "start": 11, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "callee": { + "type": "Identifier", + "start": 4, + "end": 5, + "loc": { + "start": { + "line": 1, + "column": 4 + }, + "end": { + "line": 1, + "column": 5 + }, + "identifierName": "C" + }, + "name": "C" + }, + "arguments": [], + "optional": true + } + } + ], + "directives": [] + } +} From c1702e1da6e704e96e51d983a1ff2047f4a4add9 Mon Sep 17 00:00:00 2001 From: Sven SAULEAU Date: Tue, 30 May 2017 20:16:13 +0200 Subject: [PATCH 11/13] fix: minor change --- src/parser/expression.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/expression.js b/src/parser/expression.js index 30e459c3a0..51ce2ee690 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -316,7 +316,7 @@ export default class ExpressionParser extends LValParser { node.computed = true; this.expect(tt.bracketR); base = this.finishNode(node, "MemberExpression"); - } if (this.eat(tt.parenL)) { + } else if (this.eat(tt.parenL)) { const possibleAsync = this.state.potentialArrowAt === base.start && base.type === "Identifier" && base.name === "async" && From e1ec23cd3e47d30355aebbb8996a6deeae7ff4af Mon Sep 17 00:00:00 2001 From: Justin Ridgewell Date: Sat, 3 Jun 2017 01:16:59 -0400 Subject: [PATCH 12/13] Finish optionalChaining plugin --- src/parser/expression.js | 22 +- src/tokenizer/index.js | 6 +- .../class-contructor-call/actual.js | 6 + .../class-contructor-call/expected.json | 338 +++++++++++++++++- .../conditional-decimal/actual.js | 3 + .../conditional-decimal/expected.json | 206 +++++++++++ .../conditional-decimal/options.json | 3 + .../optional-chaining/function-call/actual.js | 4 + .../function-call/expected.json | 249 ++++++++++++- .../member-access-bracket/actual.js | 6 + .../member-access-bracket/expected.json | 279 ++++++++++++++- .../optional-chaining/member-access/actual.js | 2 + .../member-access/expected.json | 112 +++++- 13 files changed, 1196 insertions(+), 40 deletions(-) create mode 100644 test/fixtures/experimental/optional-chaining/conditional-decimal/actual.js create mode 100644 test/fixtures/experimental/optional-chaining/conditional-decimal/expected.json create mode 100644 test/fixtures/experimental/optional-chaining/conditional-decimal/options.json diff --git a/src/parser/expression.js b/src/parser/expression.js index 0941a6b587..e78b8955f1 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -302,18 +302,23 @@ export default class ExpressionParser extends LValParser { node.callee = this.parseNoCallExpr(); return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls); - } else if (this.eat(tt.questionDot)) { - const node = this.startNodeAt(startPos, startLoc); - + } else if (this.match(tt.questionDot)) { if (!this.hasPlugin("optionalChaining")) { - this.raise(node.start, "You can only use optional-chaining when the 'optionalChaining' plugin is enabled."); + 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.optional = true; 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)) { @@ -322,6 +327,7 @@ export default class ExpressionParser extends LValParser { base.name === "async" && !this.canInsertSemicolon(); + node.callee = base; node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); node.optional = true; @@ -329,8 +335,8 @@ export default class ExpressionParser extends LValParser { } else { node.object = base; node.property = this.parseIdentifier(true); - node.optional = true; node.computed = false; + node.optional = true; base = this.finishNode(node, "MemberExpression"); } } else if (this.eat(tt.dot)) { @@ -768,6 +774,7 @@ 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); @@ -775,6 +782,9 @@ export default class ExpressionParser extends LValParser { } else { node.arguments = []; } + if (optional) { + node.optional = true; + } return this.finishNode(node, "NewExpression"); } diff --git a/src/tokenizer/index.js b/src/tokenizer/index.js index 538597df3a..460538f838 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -428,11 +428,11 @@ export default class Tokenizer extends LocationParser { readToken_question() { // '?' const next = this.input.charCodeAt(this.state.pos + 1); - if (next === 46) { // '.' + 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 { + } else { ++this.state.pos; return this.finishToken(tt.question); } diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js index e08331157e..8cefe81821 100644 --- a/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js @@ -1,3 +1,9 @@ new C?.() new C?.(a, b) + +new B?.C?.() + +new B?.C?.(a, b) + +new B?.C diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json b/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json index 38e9ba308e..cd91d5b86e 100644 --- a/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json @@ -1,29 +1,29 @@ { "type": "File", "start": 0, - "end": 24, + "end": 66, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 3, - "column": 13 + "line": 9, + "column": 8 } }, "program": { "type": "Program", "start": 0, - "end": 24, + "end": 66, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 3, - "column": 13 + "line": 9, + "column": 8 } }, "sourceType": "script", @@ -107,24 +107,342 @@ }, "callee": { "type": "Identifier", - "start": 4, - "end": 5, + "start": 15, + "end": 16, "loc": { "start": { - "line": 1, + "line": 3, "column": 4 }, "end": { - "line": 1, + "line": 3, "column": 5 }, "identifierName": "C" }, "name": "C" }, + "arguments": [ + { + "type": "Identifier", + "start": 19, + "end": 20, + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + }, + "identifierName": "a" + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 22, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + }, + "identifierName": "b" + }, + "name": "b" + } + ], + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 26, + "end": 38, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "expression": { + "type": "NewExpression", + "start": 26, + "end": 38, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 12 + } + }, + "callee": { + "type": "MemberExpression", + "start": 30, + "end": 34, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 8 + } + }, + "object": { + "type": "Identifier", + "start": 30, + "end": 31, + "loc": { + "start": { + "line": 5, + "column": 4 + }, + "end": { + "line": 5, + "column": 5 + }, + "identifierName": "B" + }, + "name": "B" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 33, + "end": 34, + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 8 + }, + "identifierName": "C" + }, + "name": "C" + }, + "computed": false + }, "arguments": [], "optional": true } + }, + { + "type": "ExpressionStatement", + "start": 40, + "end": 56, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 16 + } + }, + "expression": { + "type": "NewExpression", + "start": 40, + "end": 56, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 16 + } + }, + "callee": { + "type": "MemberExpression", + "start": 44, + "end": 48, + "loc": { + "start": { + "line": 7, + "column": 4 + }, + "end": { + "line": 7, + "column": 8 + } + }, + "object": { + "type": "Identifier", + "start": 44, + "end": 45, + "loc": { + "start": { + "line": 7, + "column": 4 + }, + "end": { + "line": 7, + "column": 5 + }, + "identifierName": "B" + }, + "name": "B" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 47, + "end": 48, + "loc": { + "start": { + "line": 7, + "column": 7 + }, + "end": { + "line": 7, + "column": 8 + }, + "identifierName": "C" + }, + "name": "C" + }, + "computed": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 51, + "end": 52, + "loc": { + "start": { + "line": 7, + "column": 11 + }, + "end": { + "line": 7, + "column": 12 + }, + "identifierName": "a" + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 54, + "end": 55, + "loc": { + "start": { + "line": 7, + "column": 14 + }, + "end": { + "line": 7, + "column": 15 + }, + "identifierName": "b" + }, + "name": "b" + } + ], + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 58, + "end": 66, + "loc": { + "start": { + "line": 9, + "column": 0 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "expression": { + "type": "NewExpression", + "start": 58, + "end": 66, + "loc": { + "start": { + "line": 9, + "column": 0 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "callee": { + "type": "MemberExpression", + "start": 62, + "end": 66, + "loc": { + "start": { + "line": 9, + "column": 4 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "object": { + "type": "Identifier", + "start": 62, + "end": 63, + "loc": { + "start": { + "line": 9, + "column": 4 + }, + "end": { + "line": 9, + "column": 5 + }, + "identifierName": "B" + }, + "name": "B" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 65, + "end": 66, + "loc": { + "start": { + "line": 9, + "column": 7 + }, + "end": { + "line": 9, + "column": 8 + }, + "identifierName": "C" + }, + "name": "C" + }, + "computed": false + }, + "arguments": [] + } } ], "directives": [] diff --git a/test/fixtures/experimental/optional-chaining/conditional-decimal/actual.js b/test/fixtures/experimental/optional-chaining/conditional-decimal/actual.js new file mode 100644 index 0000000000..d45b871d97 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/conditional-decimal/actual.js @@ -0,0 +1,3 @@ +true?.3:0 + +true ? .3 : 0 diff --git a/test/fixtures/experimental/optional-chaining/conditional-decimal/expected.json b/test/fixtures/experimental/optional-chaining/conditional-decimal/expected.json new file mode 100644 index 0000000000..a7c530f542 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/conditional-decimal/expected.json @@ -0,0 +1,206 @@ +{ + "type": "File", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "expression": { + "type": "ConditionalExpression", + "start": 0, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "test": { + "type": "BooleanLiteral", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + } + }, + "value": true + }, + "consequent": { + "type": "NumericLiteral", + "start": 5, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "extra": { + "rawValue": 0.3, + "raw": ".3" + }, + "value": 0.3 + }, + "alternate": { + "type": "NumericLiteral", + "start": 8, + "end": 9, + "loc": { + "start": { + "line": 1, + "column": 8 + }, + "end": { + "line": 1, + "column": 9 + } + }, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + }, + { + "type": "ExpressionStatement", + "start": 11, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "expression": { + "type": "ConditionalExpression", + "start": 11, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "test": { + "type": "BooleanLiteral", + "start": 11, + "end": 15, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 4 + } + }, + "value": true + }, + "consequent": { + "type": "NumericLiteral", + "start": 18, + "end": 20, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 9 + } + }, + "extra": { + "rawValue": 0.3, + "raw": ".3" + }, + "value": 0.3 + }, + "alternate": { + "type": "NumericLiteral", + "start": 23, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 12 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "extra": { + "rawValue": 0, + "raw": "0" + }, + "value": 0 + } + } + } + ], + "directives": [] + } +} diff --git a/test/fixtures/experimental/optional-chaining/conditional-decimal/options.json b/test/fixtures/experimental/optional-chaining/conditional-decimal/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/conditional-decimal/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +} diff --git a/test/fixtures/experimental/optional-chaining/function-call/actual.js b/test/fixtures/experimental/optional-chaining/function-call/actual.js index 7a5a8c001b..ccf7cad6ee 100644 --- a/test/fixtures/experimental/optional-chaining/function-call/actual.js +++ b/test/fixtures/experimental/optional-chaining/function-call/actual.js @@ -1,3 +1,7 @@ func?.() func?.(a, b) + +a?.func?.() + +a?.func?.(a, b) diff --git a/test/fixtures/experimental/optional-chaining/function-call/expected.json b/test/fixtures/experimental/optional-chaining/function-call/expected.json index 60248b0e79..6b21fceb08 100644 --- a/test/fixtures/experimental/optional-chaining/function-call/expected.json +++ b/test/fixtures/experimental/optional-chaining/function-call/expected.json @@ -1,29 +1,29 @@ { "type": "File", "start": 0, - "end": 22, + "end": 52, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 3, - "column": 12 + "line": 7, + "column": 15 } }, "program": { "type": "Program", "start": 0, - "end": 22, + "end": 52, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 3, - "column": 12 + "line": 7, + "column": 15 } }, "sourceType": "script", @@ -56,6 +56,23 @@ "column": 8 } }, + "callee": { + "type": "Identifier", + "start": 0, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 4 + }, + "identifierName": "func" + }, + "name": "func" + }, "arguments": [], "optional": true } @@ -88,6 +105,23 @@ "column": 12 } }, + "callee": { + "type": "Identifier", + "start": 10, + "end": 14, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 4 + }, + "identifierName": "func" + }, + "name": "func" + }, "arguments": [ { "type": "Identifier", @@ -126,8 +160,209 @@ ], "optional": true } + }, + { + "type": "ExpressionStatement", + "start": 24, + "end": 35, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "expression": { + "type": "CallExpression", + "start": 24, + "end": 35, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "callee": { + "type": "MemberExpression", + "start": 24, + "end": 31, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 7 + } + }, + "object": { + "type": "Identifier", + "start": 24, + "end": 25, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 1 + }, + "identifierName": "a" + }, + "name": "a" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 27, + "end": 31, + "loc": { + "start": { + "line": 5, + "column": 3 + }, + "end": { + "line": 5, + "column": 7 + }, + "identifierName": "func" + }, + "name": "func" + }, + "computed": false + }, + "arguments": [], + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 37, + "end": 52, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 15 + } + }, + "expression": { + "type": "CallExpression", + "start": 37, + "end": 52, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 15 + } + }, + "callee": { + "type": "MemberExpression", + "start": 37, + "end": 44, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 7 + } + }, + "object": { + "type": "Identifier", + "start": 37, + "end": 38, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 1 + }, + "identifierName": "a" + }, + "name": "a" + }, + "optional": true, + "property": { + "type": "Identifier", + "start": 40, + "end": 44, + "loc": { + "start": { + "line": 7, + "column": 3 + }, + "end": { + "line": 7, + "column": 7 + }, + "identifierName": "func" + }, + "name": "func" + }, + "computed": false + }, + "arguments": [ + { + "type": "Identifier", + "start": 47, + "end": 48, + "loc": { + "start": { + "line": 7, + "column": 10 + }, + "end": { + "line": 7, + "column": 11 + }, + "identifierName": "a" + }, + "name": "a" + }, + { + "type": "Identifier", + "start": 50, + "end": 51, + "loc": { + "start": { + "line": 7, + "column": 13 + }, + "end": { + "line": 7, + "column": 14 + }, + "identifierName": "b" + }, + "name": "b" + } + ], + "optional": true + } } ], "directives": [] } -} \ No newline at end of file +} diff --git a/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js b/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js index faf3fc3ac0..ee45bfdb9e 100644 --- a/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js +++ b/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js @@ -1 +1,7 @@ obj?.[expr] + +obj?.[expr]?.[other] + +obj?.[true] + +obj?.[true]?.[true] diff --git a/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json b/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json index cd6a6d9c88..aa2588dde0 100644 --- a/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json +++ b/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json @@ -1,29 +1,29 @@ { "type": "File", "start": 0, - "end": 11, + "end": 67, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 1, - "column": 11 + "line": 7, + "column": 19 } }, "program": { "type": "Program", "start": 0, - "end": 11, + "end": 67, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 1, - "column": 11 + "line": 7, + "column": 19 } }, "sourceType": "script", @@ -73,7 +73,6 @@ }, "name": "obj" }, - "optional": true, "property": { "type": "Identifier", "start": 6, @@ -91,7 +90,271 @@ }, "name": "expr" }, - "computed": true + "computed": true, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 13, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "expression": { + "type": "MemberExpression", + "start": 13, + "end": 33, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 20 + } + }, + "object": { + "type": "MemberExpression", + "start": 13, + "end": 24, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 11 + } + }, + "object": { + "type": "Identifier", + "start": 13, + "end": 16, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + }, + "identifierName": "obj" + }, + "name": "obj" + }, + "property": { + "type": "Identifier", + "start": 19, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 6 + }, + "end": { + "line": 3, + "column": 10 + }, + "identifierName": "expr" + }, + "name": "expr" + }, + "computed": true, + "optional": true + }, + "property": { + "type": "Identifier", + "start": 27, + "end": 32, + "loc": { + "start": { + "line": 3, + "column": 14 + }, + "end": { + "line": 3, + "column": 19 + }, + "identifierName": "other" + }, + "name": "other" + }, + "computed": true, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 35, + "end": 46, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "expression": { + "type": "MemberExpression", + "start": 35, + "end": 46, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 11 + } + }, + "object": { + "type": "Identifier", + "start": 35, + "end": 38, + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 3 + }, + "identifierName": "obj" + }, + "name": "obj" + }, + "property": { + "type": "BooleanLiteral", + "start": 41, + "end": 45, + "loc": { + "start": { + "line": 5, + "column": 6 + }, + "end": { + "line": 5, + "column": 10 + } + }, + "value": true + }, + "computed": true, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 48, + "end": 67, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 19 + } + }, + "expression": { + "type": "MemberExpression", + "start": 48, + "end": 67, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 19 + } + }, + "object": { + "type": "MemberExpression", + "start": 48, + "end": 59, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 11 + } + }, + "object": { + "type": "Identifier", + "start": 48, + "end": 51, + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 3 + }, + "identifierName": "obj" + }, + "name": "obj" + }, + "property": { + "type": "BooleanLiteral", + "start": 54, + "end": 58, + "loc": { + "start": { + "line": 7, + "column": 6 + }, + "end": { + "line": 7, + "column": 10 + } + }, + "value": true + }, + "computed": true, + "optional": true + }, + "property": { + "type": "BooleanLiteral", + "start": 62, + "end": 66, + "loc": { + "start": { + "line": 7, + "column": 14 + }, + "end": { + "line": 7, + "column": 18 + } + }, + "value": true + }, + "computed": true, + "optional": true } } ], diff --git a/test/fixtures/experimental/optional-chaining/member-access/actual.js b/test/fixtures/experimental/optional-chaining/member-access/actual.js index 6ed5c83b93..8e44f57415 100644 --- a/test/fixtures/experimental/optional-chaining/member-access/actual.js +++ b/test/fixtures/experimental/optional-chaining/member-access/actual.js @@ -1 +1,3 @@ foo?.bar + +foo?.bar?.baz diff --git a/test/fixtures/experimental/optional-chaining/member-access/expected.json b/test/fixtures/experimental/optional-chaining/member-access/expected.json index 868d2a3e08..f3e110ea14 100644 --- a/test/fixtures/experimental/optional-chaining/member-access/expected.json +++ b/test/fixtures/experimental/optional-chaining/member-access/expected.json @@ -1,29 +1,29 @@ { "type": "File", "start": 0, - "end": 8, + "end": 23, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 1, - "column": 8 + "line": 3, + "column": 13 } }, "program": { "type": "Program", "start": 0, - "end": 8, + "end": 23, "loc": { "start": { "line": 1, "column": 0 }, "end": { - "line": 1, - "column": 8 + "line": 3, + "column": 13 } }, "sourceType": "script", @@ -93,6 +93,106 @@ "optional": true, "computed": false } + }, + { + "type": "ExpressionStatement", + "start": 10, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "expression": { + "type": "MemberExpression", + "start": 10, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "object": { + "type": "MemberExpression", + "start": 10, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 8 + } + }, + "object": { + "type": "Identifier", + "start": 10, + "end": 13, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + }, + "identifierName": "foo" + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 15, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 5 + }, + "end": { + "line": 3, + "column": 8 + }, + "identifierName": "bar" + }, + "name": "bar" + }, + "optional": true, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 20, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 10 + }, + "end": { + "line": 3, + "column": 13 + }, + "identifierName": "baz" + }, + "name": "baz" + }, + "optional": true, + "computed": false + } } ], "directives": [] From 4c8f4a23db832569a6de0fd6f14902340a12c5e6 Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Mon, 5 Jun 2017 16:51:45 -0400 Subject: [PATCH 13/13] add another test --- .../separated-chaining/actual.js | 3 + .../separated-chaining/expected.json | 431 ++++++++++++++++++ .../separated-chaining/options.json | 3 + 3 files changed, 437 insertions(+) create mode 100644 test/fixtures/experimental/optional-chaining/separated-chaining/actual.js create mode 100644 test/fixtures/experimental/optional-chaining/separated-chaining/expected.json create mode 100644 test/fixtures/experimental/optional-chaining/separated-chaining/options.json diff --git a/test/fixtures/experimental/optional-chaining/separated-chaining/actual.js b/test/fixtures/experimental/optional-chaining/separated-chaining/actual.js new file mode 100644 index 0000000000..41f0670294 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/separated-chaining/actual.js @@ -0,0 +1,3 @@ +a?.b.c.d.e?.f + +a.b.c?.d.e.f diff --git a/test/fixtures/experimental/optional-chaining/separated-chaining/expected.json b/test/fixtures/experimental/optional-chaining/separated-chaining/expected.json new file mode 100644 index 0000000000..49d8792a94 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/separated-chaining/expected.json @@ -0,0 +1,431 @@ +{ + "type": "File", + "start": 0, + "end": 27, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 27, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "expression": { + "type": "MemberExpression", + "start": 0, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 13 + } + }, + "object": { + "type": "MemberExpression", + "start": 0, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "object": { + "type": "MemberExpression", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "object": { + "type": "MemberExpression", + "start": 0, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 6 + } + }, + "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 + }, + "identifierName": "a" + }, + "name": "a" + }, + "property": { + "type": "Identifier", + "start": 3, + "end": 4, + "loc": { + "start": { + "line": 1, + "column": 3 + }, + "end": { + "line": 1, + "column": 4 + }, + "identifierName": "b" + }, + "name": "b" + }, + "computed": false, + "optional": true + }, + "property": { + "type": "Identifier", + "start": 5, + "end": 6, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 6 + }, + "identifierName": "c" + }, + "name": "c" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 7, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "d" + }, + "name": "d" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 9, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 9 + }, + "end": { + "line": 1, + "column": 10 + }, + "identifierName": "e" + }, + "name": "e" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 12, + "end": 13, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 13 + }, + "identifierName": "f" + }, + "name": "f" + }, + "computed": false, + "optional": true + } + }, + { + "type": "ExpressionStatement", + "start": 15, + "end": 27, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "expression": { + "type": "MemberExpression", + "start": 15, + "end": 27, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 12 + } + }, + "object": { + "type": "MemberExpression", + "start": 15, + "end": 25, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 10 + } + }, + "object": { + "type": "MemberExpression", + "start": 15, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 8 + } + }, + "object": { + "type": "MemberExpression", + "start": 15, + "end": 20, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 5 + } + }, + "object": { + "type": "MemberExpression", + "start": 15, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 3 + } + }, + "object": { + "type": "Identifier", + "start": 15, + "end": 16, + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + }, + "identifierName": "a" + }, + "name": "a" + }, + "property": { + "type": "Identifier", + "start": 17, + "end": 18, + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 3 + }, + "identifierName": "b" + }, + "name": "b" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 19, + "end": 20, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 5 + }, + "identifierName": "c" + }, + "name": "c" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 22, + "end": 23, + "loc": { + "start": { + "line": 3, + "column": 7 + }, + "end": { + "line": 3, + "column": 8 + }, + "identifierName": "d" + }, + "name": "d" + }, + "computed": false, + "optional": true + }, + "property": { + "type": "Identifier", + "start": 24, + "end": 25, + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 3, + "column": 10 + }, + "identifierName": "e" + }, + "name": "e" + }, + "computed": false + }, + "property": { + "type": "Identifier", + "start": 26, + "end": 27, + "loc": { + "start": { + "line": 3, + "column": 11 + }, + "end": { + "line": 3, + "column": 12 + }, + "identifierName": "f" + }, + "name": "f" + }, + "computed": false + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/experimental/optional-chaining/separated-chaining/options.json b/test/fixtures/experimental/optional-chaining/separated-chaining/options.json new file mode 100644 index 0000000000..fd201c1bdb --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/separated-chaining/options.json @@ -0,0 +1,3 @@ +{ + "plugins": ["optionalChaining"] +}