From d15a2310cd405cb0bbfae1fbeb12d8ffbd4e28cb Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Sat, 12 Mar 2016 14:04:53 +0100 Subject: [PATCH] Fix flow plugin when flow+arrow+spread used together The fix includes creating a new method on the parser called `parseArrow`. This new function by default only checks if current position matches an arrow. If it does returns the `node` otherwise `undefined`. The flow plugin can then extend this function and correctly parse the typeAnnotation and add it to the node. With this change, in the flow plugin there is no need anymore to extend `parseParenAndDistinguishExpression` and the arrow handling in `parseParenItem` could also be removed, because it is all handled now in `parseArrow`. Some existing tests were failing, because `extra->parentesized` is now missing, but this is correct as it is now inline with parsing without flow annotation. No extra is added for arrow function without type annotations. In the expression-parser `this.next()` was replaced by a more specific `this.expect(tt.parenL)`. --- src/parser/expression.js | 17 +- src/plugins/flow.js | 72 ++-- .../flow/regression/issue-2493/expected.json | 4 - .../flow/type-annotations/101/actual.js | 1 + .../flow/type-annotations/101/expected.json | 232 +++++++++++++ .../flow/type-annotations/102/actual.js | 1 + .../flow/type-annotations/102/expected.json | 196 +++++++++++ .../flow/type-annotations/103/actual.js | 1 + .../flow/type-annotations/103/expected.json | 323 ++++++++++++++++++ .../flow/type-annotations/68/expected.json | 5 +- .../flow/type-annotations/73/expected.json | 5 +- .../flow/type-annotations/74/expected.json | 5 +- .../flow/type-annotations/79/expected.json | 5 +- 13 files changed, 793 insertions(+), 74 deletions(-) create mode 100644 test/fixtures/flow/type-annotations/101/actual.js create mode 100644 test/fixtures/flow/type-annotations/101/expected.json create mode 100644 test/fixtures/flow/type-annotations/102/actual.js create mode 100644 test/fixtures/flow/type-annotations/102/expected.json create mode 100644 test/fixtures/flow/type-annotations/103/actual.js create mode 100644 test/fixtures/flow/type-annotations/103/expected.json diff --git a/src/parser/expression.js b/src/parser/expression.js index bc89b4bdf5..e01df389c1 100644 --- a/src/parser/expression.js +++ b/src/parser/expression.js @@ -531,12 +531,12 @@ pp.parseParenExpression = function () { return val; }; -pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow, isAsync, allowOptionalCommaStart) { +pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow, isAsync) { startPos = startPos || this.state.start; startLoc = startLoc || this.state.startLoc; let val; - this.next(); + this.expect(tt.parenL); let innerStartPos = this.state.start, innerStartLoc = this.state.startLoc; let exprList = [], first = true; @@ -566,12 +566,13 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow let innerEndLoc = this.state.startLoc; this.expect(tt.parenR); - if (canBeArrow && !this.canInsertSemicolon() && this.eat(tt.arrow)) { + let arrowNode = this.startNodeAt(startPos, startLoc); + if (canBeArrow && !this.canInsertSemicolon() && (arrowNode = this.parseArrow(arrowNode))) { for (let param of exprList) { if (param.extra && param.extra.parenthesized) this.unexpected(param.extra.parenStart); } - return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList, isAsync); + return this.parseArrowExpression(arrowNode, exprList, isAsync); } if (!exprList.length) { @@ -581,7 +582,7 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow this.unexpected(this.state.lastTokStart); } } - if (optionalCommaStart && !allowOptionalCommaStart) this.unexpected(optionalCommaStart); + if (optionalCommaStart) this.unexpected(optionalCommaStart); if (spreadStart) this.unexpected(spreadStart); if (refShorthandDefaultPos.start) this.unexpected(refShorthandDefaultPos.start); @@ -601,6 +602,12 @@ pp.parseParenAndDistinguishExpression = function (startPos, startLoc, canBeArrow return val; }; +pp.parseArrow = function (node) { + if (this.eat(tt.arrow)) { + return node; + } +}; + pp.parseParenItem = function (node) { return node; }; diff --git a/src/plugins/flow.js b/src/plugins/flow.js index 3ecaeb84e2..f9d65b7e73 100644 --- a/src/plugins/flow.js +++ b/src/plugins/flow.js @@ -717,30 +717,19 @@ export default function (instance) { }; }); - instance.extend("parseParenItem", function () { - return function (node, startLoc, startPos, forceArrow?) { - let canBeArrow = this.state.potentialArrowAt = startPos; + instance.extend("parseParenItem", function (inner) { + return function (node, startLoc, startPos) { + node = inner.call(this, node, startLoc, startPos); + if (this.match(tt.colon)) { let typeCastNode = this.startNodeAt(startLoc, startPos); typeCastNode.expression = node; typeCastNode.typeAnnotation = this.flowParseTypeAnnotation(); - if (forceArrow && !this.match(tt.arrow)) { - this.unexpected(); - } - - if (canBeArrow && this.eat(tt.arrow)) { - // ((lol): number => {}); - let params = node.type === "SequenceExpression" ? node.expressions : [node]; - let func = this.parseArrowExpression(this.startNodeAt(startLoc, startPos), params); - func.returnType = typeCastNode.typeAnnotation; - return func; - } else { - return this.finishNode(typeCastNode, "TypeCastExpression"); - } - } else { - return node; + return this.finishNode(typeCastNode, "TypeCastExpression"); } + + return node; }; }); @@ -1038,40 +1027,25 @@ export default function (instance) { }); // handle return types for arrow functions - instance.extend("parseParenAndDistinguishExpression", function (inner) { - return function (startPos, startLoc, canBeArrow, isAsync) { - startPos = startPos || this.state.start; - startLoc = startLoc || this.state.startLoc; - - if (canBeArrow && this.lookahead().type === tt.parenR) { - // let foo = (): number => {}; - this.expect(tt.parenL); - this.expect(tt.parenR); - - let node = this.startNodeAt(startPos, startLoc); - if (this.match(tt.colon)) node.returnType = this.flowParseTypeAnnotation(); - this.expect(tt.arrow); - return this.parseArrowExpression(node, [], isAsync); - } else { - // let foo = (foo): number => {}; - let node = inner.call(this, startPos, startLoc, canBeArrow, isAsync, this.hasPlugin("trailingFunctionCommas")); - - if (this.match(tt.colon)) { - let state = this.state.clone(); - try { - return this.parseParenItem(node, startPos, startLoc, true); - } catch (err) { - if (err instanceof SyntaxError) { - this.state = state; - return node; - } else { - throw err; - } + instance.extend("parseArrow", function (inner) { + return function (node) { + if (this.match(tt.colon)) { + let state = this.state.clone(); + try { + let returnType = this.flowParseTypeAnnotation(); + if (!this.match(tt.arrow)) this.unexpected(); + // assign after it is clear it is an arrow + node.returnType = returnType; + } catch (err) { + if (err instanceof SyntaxError) { + this.state = state; + } else { + throw err; } - } else { - return node; } } + + return inner.call(this, node); }; }); } diff --git a/test/fixtures/flow/regression/issue-2493/expected.json b/test/fixtures/flow/regression/issue-2493/expected.json index cba658c02f..3cd5ae4637 100644 --- a/test/fixtures/flow/regression/issue-2493/expected.json +++ b/test/fixtures/flow/regression/issue-2493/expected.json @@ -171,10 +171,6 @@ "raw": "' world'" }, "value": " world" - }, - "extra": { - "parenthesized": true, - "parenStart": 12 } } ], diff --git a/test/fixtures/flow/type-annotations/101/actual.js b/test/fixtures/flow/type-annotations/101/actual.js new file mode 100644 index 0000000000..be74d7b115 --- /dev/null +++ b/test/fixtures/flow/type-annotations/101/actual.js @@ -0,0 +1 @@ +( ...props: SomeType ) : ?ReturnType => ( 3 ); diff --git a/test/fixtures/flow/type-annotations/101/expected.json b/test/fixtures/flow/type-annotations/101/expected.json new file mode 100644 index 0000000000..7cc317bf8f --- /dev/null +++ b/test/fixtures/flow/type-annotations/101/expected.json @@ -0,0 +1,232 @@ +{ + "type": "File", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExpressionStatement", + "start": 0, + "end": 46, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 46 + } + }, + "expression": { + "type": "ArrowFunctionExpression", + "start": 0, + "end": 45, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 45 + } + }, + "returnType": { + "type": "TypeAnnotation", + "start": 23, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 23 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "typeAnnotation": { + "type": "NullableTypeAnnotation", + "start": 25, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 25 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 26, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 26, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "name": "ReturnType" + } + } + } + }, + "id": null, + "generator": false, + "expression": true, + "async": false, + "params": [ + { + "type": "RestElement", + "start": 2, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 2 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "argument": { + "type": "Identifier", + "start": 5, + "end": 10, + "loc": { + "start": { + "line": 1, + "column": 5 + }, + "end": { + "line": 1, + "column": 10 + } + }, + "name": "props" + }, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 10, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 10 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 12, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 12, + "end": 20, + "loc": { + "start": { + "line": 1, + "column": 12 + }, + "end": { + "line": 1, + "column": 20 + } + }, + "name": "SomeType" + } + } + } + } + ], + "body": { + "type": "NumericLiteral", + "start": 42, + "end": 43, + "loc": { + "start": { + "line": 1, + "column": 42 + }, + "end": { + "line": 1, + "column": 43 + } + }, + "extra": { + "rawValue": 3, + "raw": "3", + "parenthesized": true, + "parenStart": 40 + }, + "value": 3 + } + } + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/flow/type-annotations/102/actual.js b/test/fixtures/flow/type-annotations/102/actual.js new file mode 100644 index 0000000000..406c4a703d --- /dev/null +++ b/test/fixtures/flow/type-annotations/102/actual.js @@ -0,0 +1 @@ +export default (...modifiers): Array => {}; diff --git a/test/fixtures/flow/type-annotations/102/expected.json b/test/fixtures/flow/type-annotations/102/expected.json new file mode 100644 index 0000000000..4af93bb673 --- /dev/null +++ b/test/fixtures/flow/type-annotations/102/expected.json @@ -0,0 +1,196 @@ +{ + "type": "File", + "start": 0, + "end": 51, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 51 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 51, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 51 + } + }, + "sourceType": "module", + "body": [ + { + "type": "ExportDefaultDeclaration", + "start": 0, + "end": 51, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 51 + } + }, + "declaration": { + "type": "ArrowFunctionExpression", + "start": 15, + "end": 50, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 50 + } + }, + "returnType": { + "type": "TypeAnnotation", + "start": 29, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 29 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 31, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "typeParameters": { + "type": "TypeParameterInstantiation", + "start": 36, + "end": 44, + "loc": { + "start": { + "line": 1, + "column": 36 + }, + "end": { + "line": 1, + "column": 44 + } + }, + "params": [ + { + "type": "StringTypeAnnotation", + "start": 37, + "end": 43, + "loc": { + "start": { + "line": 1, + "column": 37 + }, + "end": { + "line": 1, + "column": 43 + } + } + } + ] + }, + "id": { + "type": "Identifier", + "start": 31, + "end": 36, + "loc": { + "start": { + "line": 1, + "column": 31 + }, + "end": { + "line": 1, + "column": 36 + } + }, + "name": "Array" + } + } + }, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "RestElement", + "start": 16, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 28 + } + }, + "argument": { + "type": "Identifier", + "start": 19, + "end": 28, + "loc": { + "start": { + "line": 1, + "column": 19 + }, + "end": { + "line": 1, + "column": 28 + } + }, + "name": "modifiers" + } + } + ], + "body": { + "type": "BlockStatement", + "start": 48, + "end": 50, + "loc": { + "start": { + "line": 1, + "column": 48 + }, + "end": { + "line": 1, + "column": 50 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "directives": [] + } +} diff --git a/test/fixtures/flow/type-annotations/103/actual.js b/test/fixtures/flow/type-annotations/103/actual.js new file mode 100644 index 0000000000..b6a472b531 --- /dev/null +++ b/test/fixtures/flow/type-annotations/103/actual.js @@ -0,0 +1 @@ +const parser = (rootPath: string, ...filesToParse: Array):a => {} diff --git a/test/fixtures/flow/type-annotations/103/expected.json b/test/fixtures/flow/type-annotations/103/expected.json new file mode 100644 index 0000000000..5eda80448f --- /dev/null +++ b/test/fixtures/flow/type-annotations/103/expected.json @@ -0,0 +1,323 @@ +{ + "type": "File", + "start": 0, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "program": { + "type": "Program", + "start": 0, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "sourceType": "module", + "body": [ + { + "type": "VariableDeclaration", + "start": 0, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "declarations": [ + { + "type": "VariableDeclarator", + "start": 6, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "id": { + "type": "Identifier", + "start": 6, + "end": 12, + "loc": { + "start": { + "line": 1, + "column": 6 + }, + "end": { + "line": 1, + "column": 12 + } + }, + "name": "parser" + }, + "init": { + "type": "ArrowFunctionExpression", + "start": 15, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 15 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "returnType": { + "type": "TypeAnnotation", + "start": 65, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 65 + }, + "end": { + "line": 1, + "column": 67 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 66, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 66 + }, + "end": { + "line": 1, + "column": 67 + } + }, + "typeParameters": null, + "id": { + "type": "Identifier", + "start": 66, + "end": 67, + "loc": { + "start": { + "line": 1, + "column": 66 + }, + "end": { + "line": 1, + "column": 67 + } + }, + "name": "a" + } + } + }, + "id": null, + "generator": false, + "expression": false, + "async": false, + "params": [ + { + "type": "Identifier", + "start": 16, + "end": 24, + "loc": { + "start": { + "line": 1, + "column": 16 + }, + "end": { + "line": 1, + "column": 24 + } + }, + "name": "rootPath", + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 24, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 24 + }, + "end": { + "line": 1, + "column": 32 + } + }, + "typeAnnotation": { + "type": "StringTypeAnnotation", + "start": 26, + "end": 32, + "loc": { + "start": { + "line": 1, + "column": 26 + }, + "end": { + "line": 1, + "column": 32 + } + } + } + } + }, + { + "type": "RestElement", + "start": 34, + "end": 49, + "loc": { + "start": { + "line": 1, + "column": 34 + }, + "end": { + "line": 1, + "column": 49 + } + }, + "argument": { + "type": "Identifier", + "start": 37, + "end": 49, + "loc": { + "start": { + "line": 1, + "column": 37 + }, + "end": { + "line": 1, + "column": 49 + } + }, + "name": "filesToParse" + }, + "typeAnnotation": { + "type": "TypeAnnotation", + "start": 49, + "end": 64, + "loc": { + "start": { + "line": 1, + "column": 49 + }, + "end": { + "line": 1, + "column": 64 + } + }, + "typeAnnotation": { + "type": "GenericTypeAnnotation", + "start": 51, + "end": 64, + "loc": { + "start": { + "line": 1, + "column": 51 + }, + "end": { + "line": 1, + "column": 64 + } + }, + "typeParameters": { + "type": "TypeParameterInstantiation", + "start": 56, + "end": 64, + "loc": { + "start": { + "line": 1, + "column": 56 + }, + "end": { + "line": 1, + "column": 64 + } + }, + "params": [ + { + "type": "StringTypeAnnotation", + "start": 57, + "end": 63, + "loc": { + "start": { + "line": 1, + "column": 57 + }, + "end": { + "line": 1, + "column": 63 + } + } + } + ] + }, + "id": { + "type": "Identifier", + "start": 51, + "end": 56, + "loc": { + "start": { + "line": 1, + "column": 51 + }, + "end": { + "line": 1, + "column": 56 + } + }, + "name": "Array" + } + } + } + } + ], + "body": { + "type": "BlockStatement", + "start": 71, + "end": 73, + "loc": { + "start": { + "line": 1, + "column": 71 + }, + "end": { + "line": 1, + "column": 73 + } + }, + "body": [], + "directives": [] + } + } + } + ], + "kind": "const" + } + ], + "directives": [] + } +} \ No newline at end of file diff --git a/test/fixtures/flow/type-annotations/68/expected.json b/test/fixtures/flow/type-annotations/68/expected.json index 0947eeb733..433bc78a8f 100644 --- a/test/fixtures/flow/type-annotations/68/expected.json +++ b/test/fixtures/flow/type-annotations/68/expected.json @@ -105,10 +105,7 @@ "column": 14 } }, - "name": "bar", - "extra": { - "parenthesized": true - } + "name": "bar" } ], "body": { diff --git a/test/fixtures/flow/type-annotations/73/expected.json b/test/fixtures/flow/type-annotations/73/expected.json index a14ef97682..3b2f689738 100644 --- a/test/fixtures/flow/type-annotations/73/expected.json +++ b/test/fixtures/flow/type-annotations/73/expected.json @@ -105,10 +105,7 @@ "column": 15 } }, - "name": "bar", - "extra": { - "parenthesized": true - } + "name": "bar" } ], "body": { diff --git a/test/fixtures/flow/type-annotations/74/expected.json b/test/fixtures/flow/type-annotations/74/expected.json index c22c079ced..2abdde67da 100644 --- a/test/fixtures/flow/type-annotations/74/expected.json +++ b/test/fixtures/flow/type-annotations/74/expected.json @@ -119,10 +119,7 @@ "column": 16 } }, - "name": "bar", - "extra": { - "parenthesized": true - } + "name": "bar" } ], "body": { diff --git a/test/fixtures/flow/type-annotations/79/expected.json b/test/fixtures/flow/type-annotations/79/expected.json index 30439bfe89..fe1dab6354 100644 --- a/test/fixtures/flow/type-annotations/79/expected.json +++ b/test/fixtures/flow/type-annotations/79/expected.json @@ -135,10 +135,7 @@ "column": 20 } }, - "name": "foo", - "extra": { - "parenthesized": true - } + "name": "foo" } ], "body": {