diff --git a/src/parser/expression.js b/src/parser/expression.js index bc89b4bdf5..a1420f9921 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -88,7 +88,7 @@ pp.parseExpression = function (noIn, refShorthandDefaultPos) { // Parse an assignment expression. This includes applications of // operators like `+=`. -pp.parseMaybeAssign = function (noIn, refShorthandDefaultPos, afterLeftParse) { +pp.parseMaybeAssign = function (noIn, refShorthandDefaultPos, afterLeftParse, refNeedsArrowPos) { if (this.match(tt._yield) && this.state.inGenerator) { return this.parseYield(); } @@ -108,7 +108,7 @@ pp.parseMaybeAssign = function (noIn, refShorthandDefaultPos, afterLeftParse) { this.state.potentialArrowAt = this.state.start; } - let left = this.parseMaybeConditional(noIn, refShorthandDefaultPos); + let left = this.parseMaybeConditional(noIn, refShorthandDefaultPos, refNeedsArrowPos); if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc); if (this.state.type.isAssign) { let node = this.startNodeAt(startPos, startLoc); @@ -142,10 +142,15 @@ pp.parseMaybeAssign = function (noIn, refShorthandDefaultPos, afterLeftParse) { // Parse a ternary conditional (`?:`) operator. -pp.parseMaybeConditional = function (noIn, refShorthandDefaultPos) { +pp.parseMaybeConditional = function (noIn, refShorthandDefaultPos, refNeedsArrowPos) { let startPos = this.state.start, startLoc = this.state.startLoc; let expr = this.parseExprOps(noIn, refShorthandDefaultPos); if (refShorthandDefaultPos && refShorthandDefaultPos.start) return expr; + + return this.parseConditional(expr, noIn, startPos, startLoc, refNeedsArrowPos); +}; + +pp.parseConditional = function (expr, noIn, startPos, startLoc) { if (this.eat(tt.question)) { let node = this.startNodeAt(startPos, startLoc); node.test = expr; @@ -541,6 +546,7 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow let innerStartPos = this.state.start, innerStartLoc = this.state.startLoc; let exprList = [], first = true; let refShorthandDefaultPos = { start: 0 }, spreadStart, optionalCommaStart; + let refNeedsArrowPos = { start: 0 }; while (!this.match(tt.parenR)) { if (first) { first = false; @@ -558,7 +564,7 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow exprList.push(this.parseParenItem(this.parseRest(), spreadNodeStartLoc, spreadNodeStartPos)); break; } else { - exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem)); + exprList.push(this.parseMaybeAssign(false, refShorthandDefaultPos, this.parseParenItem, refNeedsArrowPos)); } } @@ -584,6 +590,7 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow if (optionalCommaStart && !allowOptionalCommaStart) this.unexpected(optionalCommaStart); if (spreadStart) this.unexpected(spreadStart); if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); + if (refNeedsArrowPos.start) this.unexpected(refNeedsArrowPos.start); if (exprList.length > 1) { val = this.startNodeAt(innerStartPos, innerStartLoc); diff --git a/src/plugins/flow.js b/src/plugins/flow.js index 3ecaeb84e2..859da9e765 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -717,9 +717,30 @@ export default function (instance) { }; }); + instance.extend("parseConditional", function (inner) { + return function (expr, noIn, startPos, startLoc, refNeedsArrowPos) { + const state = this.state.clone(); + try { + return inner.call(this, expr, noIn, startPos, startLoc); + } catch (err) { + if (refNeedsArrowPos && err instanceof SyntaxError) { + this.state = state; + refNeedsArrowPos.start = this.state.start; + return expr; + } else { + throw err; + } + } + }; + }); + instance.extend("parseParenItem", function () { return function (node, startLoc, startPos, forceArrow?) { let canBeArrow = this.state.potentialArrowAt = startPos; + if (this.eat(tt.question)) { + node.optional = true; + } + if (this.match(tt.colon)) { let typeCastNode = this.startNodeAt(startLoc, startPos); typeCastNode.expression = node; diff --git a/test/fixtures/flow/optional-type/1/actual.js b/test/fixtures/flow/optional-type/1/actual.js new file mode 100644 index 0000000000..847e00eb33 --- /dev/null +++ b/test/fixtures/flow/optional-type/1/actual.js @@ -0,0 +1 @@ +const f = (x?) => {} diff --git a/test/fixtures/flow/optional-type/1/expected.json b/test/fixtures/flow/optional-type/1/expected.json new file mode 100644 index 0000000000..14de301f7b --- /dev/null +++ b/test/fixtures/flow/optional-type/1/expected.json @@ -0,0 +1,138 @@ +{ + "type": "File", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "sourceType": "module", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "name": "f" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 10, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 11, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "x", + "optional": true + } + ], + "body": { + "type": "BlockStatement", + "start": 18, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/flow/optional-type/2/actual.js b/test/fixtures/flow/optional-type/2/actual.js new file mode 100644 index 0000000000..21e036a622 --- /dev/null +++ b/test/fixtures/flow/optional-type/2/actual.js @@ -0,0 +1 @@ +const f = (x?) diff --git a/test/fixtures/flow/optional-type/2/options.json b/test/fixtures/flow/optional-type/2/options.json new file mode 100644 index 0000000000..9e093bfdcd --- /dev/null +++ b/test/fixtures/flow/optional-type/2/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Unexpected token (1:12)" +} diff --git a/test/fixtures/flow/optional-type/3/actual.js b/test/fixtures/flow/optional-type/3/actual.js new file mode 100644 index 0000000000..3c5a9750a9 --- /dev/null +++ b/test/fixtures/flow/optional-type/3/actual.js @@ -0,0 +1 @@ +const f = (x?, y?:Object = {}) => {} diff --git a/test/fixtures/flow/optional-type/3/expected.json b/test/fixtures/flow/optional-type/3/expected.json new file mode 100644 index 0000000000..1105c60134 --- /dev/null +++ b/test/fixtures/flow/optional-type/3/expected.json @@ -0,0 +1,233 @@ +{ + "type": "File", + "start": 0, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "sourceType": "module", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 7, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 7 + } + }, + "name": "f" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 10, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 11, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 11 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "x", + "optional": true + }, + { + "type": "AssignmentPattern", + "start": 15, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "left": { + "type": "Identifier", + "start": 15, + "end": 16, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 16 + } + }, + "name": "y", + "optional": true, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 17, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 17 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 18, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 18, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 18 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "name": "Object" + } + } + } + }, + "right": { + "type": "ObjectExpression", + "start": 27, + "end": 29, + "loc": { + "start": { + "line": 1, + "column": 27 + }, + "end": { + "line": 1, + "column": 29 + } + }, + "properties": [] + } + } + ], + "body": { + "type": "BlockStatement", + "start": 34, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 34 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} \ No newline at end of file