From cf32f29d413fa90d6ffd0de4c9065e01b2b0bd7d Mon Sep 17 00:00:00 2001 From: Sosuke Suzuki Date: Sat, 2 Nov 2019 17:39:18 +0900 Subject: [PATCH] Flow: Keep parentheses wrap specific FunctionTypeAnnotation (#6717) Co-Authored-By: Georgii Dolzhykov --- CHANGELOG.unreleased.md | 16 ++++ src/language-js/needs-parens.js | 19 +++- src/language-js/utils.js | 1 + .../__snapshots__/jsfmt.spec.js.snap | 86 +++++++++++++++++-- tests/flow_return_arrow/in_object_type.js | 28 ++++++ tests/flow_return_arrow/parens.js | 9 +- 6 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 tests/flow_return_arrow/in_object_type.js diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index f2d831d0..2683766f 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -1294,6 +1294,21 @@ export class User { } ``` +#### Flow: Parentheses around arrow functions' return types that have `FunctionTypeAnnotation` nested in `ObjectTypeAnnotation` ([#6717] by [@sosukesuzuki]) + +This is a workaround for a [bug](https://github.com/facebook/flow/pull/8163) in the Flow parser. Without the parentheses, the parser throws an error. + +```js +// Input +const example1 = (): { p: (string => string) } => (0: any); + +// Output (Prettier stable) +const example1 = (): { p: string => string } => (0: any); + +// Output (Prettier master) +const example1 = (): ({ p: string => string }) => (0: any); +``` + [#5682]: https://github.com/prettier/prettier/pull/5682 [#6657]: https://github.com/prettier/prettier/pull/6657 [#5910]: https://github.com/prettier/prettier/pull/5910 @@ -1336,6 +1351,7 @@ export class User { [#6673]: https://github.com/prettier/prettier/pull/6673 [#6695]: https://github.com/prettier/prettier/pull/6695 [#6694]: https://github.com/prettier/prettier/pull/6694 +[#6717]: https://github.com/prettier/prettier/pull/6717 [#6728]: https://github.com/prettier/prettier/pull/6728 [@brainkim]: https://github.com/brainkim [@duailibe]: https://github.com/duailibe diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index d8201676..5dafb312 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -7,7 +7,8 @@ const comments = require("./comments"); const { getLeftSidePathName, hasFlowShorthandAnnotationComment, - hasNakedLeftSide + hasNakedLeftSide, + hasNode } = require("./utils"); function hasClosureCompilerTypeCastComment(text, path) { @@ -758,6 +759,12 @@ function needsParens(path, options) { parent.type !== "TypeCastExpression" && parent.type !== "VariableDeclarator") ); + case "TypeAnnotation": + return ( + name === "returnType" && + parent.type === "ArrowFunctionExpression" && + includesFunctionTypeInObjectType(node) + ); } return false; @@ -814,6 +821,16 @@ function isStatement(node) { ); } +function includesFunctionTypeInObjectType(node) { + return hasNode( + node, + n1 => + (n1.type === "ObjectTypeAnnotation" && + hasNode(n1, n2 => n2.type === "FunctionTypeAnnotation" || undefined)) || + undefined + ); +} + function endsWithRightBracket(node) { switch (node.type) { case "ObjectExpression": diff --git a/src/language-js/utils.js b/src/language-js/utils.js index 245f84dd..6fb301d5 100644 --- a/src/language-js/utils.js +++ b/src/language-js/utils.js @@ -912,6 +912,7 @@ module.exports = { hasNakedLeftSide, hasNewlineBetweenOrAfterDecorators, hasNgSideEffect, + hasNode, hasPrettierIgnore, hasTrailingComment, identity, diff --git a/tests/flow_return_arrow/__snapshots__/jsfmt.spec.js.snap b/tests/flow_return_arrow/__snapshots__/jsfmt.spec.js.snap index 43b5dee4..e5140a7e 100644 --- a/tests/flow_return_arrow/__snapshots__/jsfmt.spec.js.snap +++ b/tests/flow_return_arrow/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,73 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`in_object_type.js 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +const example1 = (): ({ p: string => string }) => (0: any); +const example2 = (): ({ p: { p: string => string } }) => (0: any); +const example3 = (): ({ p: { p: { p: string => string } } }) => (0: any); +const example4 = (): ({ p: { p: ?{ p: string => string } } }) => (0: any); +const example5 = (): ({ p: { p: { p: string => string } | string } }) => + (0: any); +const example6 = (): ({ p: { p: { p: string => string } & string } }) => + (0: any); +const example7 = (): ({ p: { p: { p: [(string) => string, string] } } }) => + (0: any); +function example8(): { p: string => string } { + return (0: any); +} +function example9(): { p: { p: string => string } } { + return (0: any); +} +function example10(): { p: { p: { p: string => string } } } { + return (0: any); +} +const example11 = (): ({ p: string => string } & string) => (0: any); +const example12 = (): ({ p: string => string } | string) => (0: any); +const example13 = (): ([{ p: string => string }, string]) => (0: any); +const example14 = (): ({ p: string => string }[]) => (0: any); +const example15 = (): ({ p: { p: { p: (string => string) & string } } }) => + (0: any); +const example16 = (): ({ p: { p: { p: (string => string) | string } } }) => + (0: any); +const example17 = (): (?{ p: string => string }) => (0: any); + +=====================================output===================================== +const example1 = (): ({ p: string => string }) => (0: any); +const example2 = (): ({ p: { p: string => string } }) => (0: any); +const example3 = (): ({ p: { p: { p: string => string } } }) => (0: any); +const example4 = (): ({ p: { p: ?{ p: string => string } } }) => (0: any); +const example5 = (): ({ p: { p: { p: string => string } | string } }) => + (0: any); +const example6 = (): ({ p: { p: { p: string => string } & string } }) => + (0: any); +const example7 = (): ({ p: { p: { p: [(string) => string, string] } } }) => + (0: any); +function example8(): { p: string => string } { + return (0: any); +} +function example9(): { p: { p: string => string } } { + return (0: any); +} +function example10(): { p: { p: { p: string => string } } } { + return (0: any); +} +const example11 = (): ({ p: string => string } & string) => (0: any); +const example12 = (): ({ p: string => string } | string) => (0: any); +const example13 = (): ([{ p: string => string }, string]) => (0: any); +const example14 = (): ({ p: string => string }[]) => (0: any); +const example15 = (): ({ p: { p: { p: (string => string) & string } } }) => + (0: any); +const example16 = (): ({ p: { p: { p: (string => string) | string } } }) => + (0: any); +const example17 = (): (?{ p: string => string }) => (0: any); + +================================================================================ +`; + exports[`issue-1249.js 1`] = ` ====================================options===================================== parsers: ["flow"] @@ -60,16 +128,18 @@ parsers: ["flow"] printWidth: 80 | printWidth =====================================input====================================== -const f = (): (string => string) => {}; -const f = (): (a | string => string) => {}; -const f = (): (a & string => string) => {}; -function f(): string => string {} +const f1 = (): (string => string) => {}; +const f2 = (): ?(y => {a: b => c}) => (0: any); +const f3 = (): (a | string => string) => {}; +const f4 = (): (a & string => string) => {}; +function f5(): string => string {} =====================================output===================================== -const f = (): (string => string) => {}; -const f = (): a | (string => string) => {}; -const f = (): a & (string => string) => {}; -function f(): string => string {} +const f1 = (): (string => string) => {}; +const f2 = (): (?(y) => { a: b => c }) => (0: any); +const f3 = (): a | (string => string) => {}; +const f4 = (): a & (string => string) => {}; +function f5(): string => string {} ================================================================================ `; diff --git a/tests/flow_return_arrow/in_object_type.js b/tests/flow_return_arrow/in_object_type.js new file mode 100644 index 00000000..f94c1cd6 --- /dev/null +++ b/tests/flow_return_arrow/in_object_type.js @@ -0,0 +1,28 @@ +const example1 = (): ({ p: string => string }) => (0: any); +const example2 = (): ({ p: { p: string => string } }) => (0: any); +const example3 = (): ({ p: { p: { p: string => string } } }) => (0: any); +const example4 = (): ({ p: { p: ?{ p: string => string } } }) => (0: any); +const example5 = (): ({ p: { p: { p: string => string } | string } }) => + (0: any); +const example6 = (): ({ p: { p: { p: string => string } & string } }) => + (0: any); +const example7 = (): ({ p: { p: { p: [(string) => string, string] } } }) => + (0: any); +function example8(): { p: string => string } { + return (0: any); +} +function example9(): { p: { p: string => string } } { + return (0: any); +} +function example10(): { p: { p: { p: string => string } } } { + return (0: any); +} +const example11 = (): ({ p: string => string } & string) => (0: any); +const example12 = (): ({ p: string => string } | string) => (0: any); +const example13 = (): ([{ p: string => string }, string]) => (0: any); +const example14 = (): ({ p: string => string }[]) => (0: any); +const example15 = (): ({ p: { p: { p: (string => string) & string } } }) => + (0: any); +const example16 = (): ({ p: { p: { p: (string => string) | string } } }) => + (0: any); +const example17 = (): (?{ p: string => string }) => (0: any); diff --git a/tests/flow_return_arrow/parens.js b/tests/flow_return_arrow/parens.js index b944ca31..5b7a5243 100644 --- a/tests/flow_return_arrow/parens.js +++ b/tests/flow_return_arrow/parens.js @@ -1,4 +1,5 @@ -const f = (): (string => string) => {}; -const f = (): (a | string => string) => {}; -const f = (): (a & string => string) => {}; -function f(): string => string {} +const f1 = (): (string => string) => {}; +const f2 = (): ?(y => {a: b => c}) => (0: any); +const f3 = (): (a | string => string) => {}; +const f4 = (): (a & string => string) => {}; +function f5(): string => string {}