diff --git a/README.md b/README.md index 8e92625e34..e11a0c4cde 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)) - `importMeta` ([proposal](https://github.com/tc39/proposal-import-meta)) ### FAQ diff --git a/ast/spec.md b/ast/spec.md index f457645452..b5d6517184 100644 --- a/ast/spec.md +++ b/ast/spec.md @@ -859,10 +859,11 @@ interface MemberExpression <: Expression, Pattern { object: Expression | Super; property: Expression; computed: boolean; + optional: boolean | null; } ``` -A member expression. If `computed` is `true`, the node corresponds to a computed (`a[b]`) member expression and `property` is an `Expression`. If `computed` is `false`, the node corresponds to a static (`a.b`) member expression and `property` is an `Identifier`. +A member expression. If `computed` is `true`, the node corresponds to a computed (`a[b]`) member expression and `property` is an `Expression`. If `computed` is `false`, the node corresponds to a static (`a.b`) member expression and `property` is an `Identifier`. The `optional` flags indicates that the member expression can be called even if the object is null or undefined. If this is the object value (null/undefined) should be returned. ### BindExpression @@ -896,6 +897,7 @@ interface CallExpression <: Expression { type: "CallExpression"; callee: Expression | Super | Import; arguments: [ Expression | SpreadElement ]; + optional: boolean | null; } ``` @@ -906,6 +908,7 @@ A function or method call expression. ```js interface NewExpression <: CallExpression { type: "NewExpression"; + optional: boolean | null; } ``` diff --git a/src/parser/expression.js b/src/parser/expression.js index 13b2371fae..e78b8955f1 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -301,6 +301,44 @@ export default class ExpressionParser extends LValParser { node.object = base; node.callee = this.parseNoCallExpr(); return this.parseSubscripts(this.finishNode(node, "BindExpression"), startPos, startLoc, noCalls); + + } else if (this.match(tt.questionDot)) { + if (!this.hasPlugin("optionalChaining")) { + this.raise(startPos, "You can only use optional-chaining when the 'optionalChaining' plugin is enabled."); + } + + if (noCalls && this.lookahead().type == tt.parenL) { + return base; + } + this.next(); + + const node = this.startNodeAt(startPos, startLoc); + + if (this.eat(tt.bracketL)) { + node.object = base; + node.property = this.parseExpression(); + node.computed = true; + node.optional = true; + this.expect(tt.bracketR); + base = this.finishNode(node, "MemberExpression"); + } else if (this.eat(tt.parenL)) { + const possibleAsync = this.state.potentialArrowAt === base.start && + base.type === "Identifier" && + base.name === "async" && + !this.canInsertSemicolon(); + + node.callee = base; + node.arguments = this.parseCallExpressionArguments(tt.parenR, possibleAsync); + node.optional = true; + + base = this.finishNode(node, "CallExpression"); + } else { + node.object = base; + node.property = this.parseIdentifier(true); + node.computed = false; + node.optional = true; + base = this.finishNode(node, "MemberExpression"); + } } else if (this.eat(tt.dot)) { const node = this.startNodeAt(startPos, startLoc); node.object = base; @@ -736,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); @@ -743,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 743c3286a5..460538f838 100644 --- a/src/tokenizer/index.js +++ b/src/tokenizer/index.js @@ -426,6 +426,18 @@ export default class Tokenizer extends LocationParser { return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1); } + readToken_question() { // '?' + const next = this.input.charCodeAt(this.state.pos + 1); + const next2 = this.input.charCodeAt(this.state.pos + 2); + if (next === 46 && !(next2 >= 48 && next2 <= 57)) { // '.' not followed by a number + this.state.pos += 2; + return this.finishToken(tt.questionDot); + } else { + ++this.state.pos; + return this.finishToken(tt.question); + } + } + getTokenFromCode(code: number): void { switch (code) { @@ -469,7 +481,7 @@ export default class Tokenizer extends LocationParser { return this.finishToken(tt.colon); } - case 63: ++this.state.pos; return this.finishToken(tt.question); + case 63: return this.readToken_question(); case 64: ++this.state.pos; return this.finishToken(tt.at); case 96: // '`' @@ -917,7 +929,7 @@ export default class Tokenizer extends LocationParser { const type = this.state.type; let update; - if (type.keyword && prevType === tt.dot) { + if (type.keyword && (prevType === tt.dot || prevType === tt.questionDot)) { this.state.exprAllowed = false; } else if (update = type.updateContext) { update.call(this, prevType); diff --git a/src/tokenizer/types.js b/src/tokenizer/types.js index 48d075d2b1..12d7ee1e93 100644 --- a/src/tokenizer/types.js +++ b/src/tokenizer/types.js @@ -102,6 +102,7 @@ export const types: { [name: string]: TokenType } = { doubleColon: new TokenType("::", { beforeExpr }), dot: new TokenType("."), question: new TokenType("?", { beforeExpr }), + questionDot: new TokenType("?."), arrow: new TokenType("=>", { beforeExpr }), template: new TokenType("template"), ellipsis: new TokenType("...", { beforeExpr }), diff --git a/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js new file mode 100644 index 0000000000..8cefe81821 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/actual.js @@ -0,0 +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 new file mode 100644 index 0000000000..cd91d5b86e --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/class-contructor-call/expected.json @@ -0,0 +1,450 @@ +{ + "type": "File", + "start": 0, + "end": 66, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 66, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 9, + "column": 8 + } + }, + "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": 15, + "end": 16, + "loc": { + "start": { + "line": 3, + "column": 4 + }, + "end": { + "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/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/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 new file mode 100644 index 0000000000..ccf7cad6ee --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/function-call/actual.js @@ -0,0 +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 new file mode 100644 index 0000000000..6b21fceb08 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/function-call/expected.json @@ -0,0 +1,368 @@ +{ + "type": "File", + "start": 0, + "end": 52, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 7, + "column": 15 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 52, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 7, + "column": 15 + } + }, + "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 + } + }, + "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 + } + }, + { + "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 + } + }, + "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", + "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 + } + }, + { + "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": [] + } +} 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/actual.js b/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js new file mode 100644 index 0000000000..ee45bfdb9e --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access-bracket/actual.js @@ -0,0 +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 new file mode 100644 index 0000000000..aa2588dde0 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access-bracket/expected.json @@ -0,0 +1,363 @@ +{ + "type": "File", + "start": 0, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 7, + "column": 19 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 7, + "column": 19 + } + }, + "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" + }, + "property": { + "type": "Identifier", + "start": 6, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 10 + }, + "identifierName": "expr" + }, + "name": "expr" + }, + "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 + } + } + ], + "directives": [] + } +} \ No newline at end of file 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/actual.js b/test/fixtures/experimental/optional-chaining/member-access/actual.js new file mode 100644 index 0000000000..8e44f57415 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access/actual.js @@ -0,0 +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 new file mode 100644 index 0000000000..f3e110ea14 --- /dev/null +++ b/test/fixtures/experimental/optional-chaining/member-access/expected.json @@ -0,0 +1,200 @@ +{ + "type": "File", + "start": 0, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 23, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 13 + } + }, + "sourceType": "script", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "expression": { + "type": "MemberExpression", + "start": 0, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + }, + "object": { + "type": "Identifier", + "start": 0, + "end": 3, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 3 + }, + "identifierName": "foo" + }, + "name": "foo" + }, + "property": { + "type": "Identifier", + "start": 5, + "end": 8, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 8 + }, + "identifierName": "bar" + }, + "name": "bar" + }, + "optional": true, + "computed": false + } + }, + { + "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": [] + } +} \ No newline at end of file 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)" +} 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"] +}