diff --git a/changelog_unreleased/flow/16191.md b/changelog_unreleased/flow/16191.md
new file mode 100644
index 000000000..f1c4291f1
--- /dev/null
+++ b/changelog_unreleased/flow/16191.md
@@ -0,0 +1,23 @@
+#### Component syntax printing support for Flow (#16191 by @SamChou19815)
+
+
+```jsx
+// Input
+component MyComponent(a: string, b: number) renders SomeComponent {
+ return ;
+}
+hook useMyHook(a: string) {
+ return useState(a);
+}
+
+// Prettier stable
+// does not parse
+
+// Prettier main
+component MyComponent(a: string, b: number) renders SomeComponent {
+ return ;
+}
+hook useMyHook(a: string) {
+ return useState(a);
+}
+```
diff --git a/src/language-js/comments/handle-comments.js b/src/language-js/comments/handle-comments.js
index f47483b86..407f80b86 100644
--- a/src/language-js/comments/handle-comments.js
+++ b/src/language-js/comments/handle-comments.js
@@ -49,6 +49,7 @@ function handleOwnLineComment(context) {
handleIgnoreComments,
handleConditionalExpressionComments,
handleLastFunctionArgComments,
+ handleLastComponentArgComments,
handleMemberExpressionComments,
handleIfStatementComments,
handleWhileComments,
@@ -600,6 +601,38 @@ function handleCommentInEmptyParens({ comment, enclosingNode, text }) {
return false;
}
+function handleLastComponentArgComments({
+ comment,
+ precedingNode,
+ enclosingNode,
+ followingNode,
+ text,
+}) {
+ // "DeclareComponent" and "ComponentTypeAnnotation" definitions
+ if (
+ precedingNode?.type === "ComponentTypeParameter" &&
+ (enclosingNode?.type === "DeclareComponent" ||
+ enclosingNode?.type === "ComponentTypeAnnotation") &&
+ followingNode?.type !== "ComponentTypeParameter"
+ ) {
+ addTrailingComment(precedingNode, comment);
+ return true;
+ }
+
+ // "ComponentParameter" definitions
+ if (
+ (precedingNode?.type === "ComponentParameter" ||
+ precedingNode?.type === "RestElement") &&
+ enclosingNode?.type === "ComponentDeclaration" &&
+ getNextNonSpaceNonCommentCharacter(text, locEnd(comment)) === ")"
+ ) {
+ addTrailingComment(precedingNode, comment);
+ return true;
+ }
+
+ return false;
+}
+
function handleLastFunctionArgComments({
comment,
precedingNode,
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index c0074632b..0d9c5b52d 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -119,6 +119,8 @@ function needsParens(path, options) {
case "using":
case "yield":
case "let":
+ case "component":
+ case "hook":
case "type": {
const ancestorNeitherAsNorSatisfies = path.findAncestor(
(node) => !isBinaryCastExpression(node),
@@ -601,6 +603,16 @@ function needsParens(path, options) {
(key === "objectType" && parent.type === "TSIndexedAccessType") ||
(key === "elementType" && parent.type === "TSArrayType")
);
+ // Same as `TSTypeOperator`, but for Flow syntax
+ case "TypeOperator":
+ return (
+ parent.type === "ArrayTypeAnnotation" ||
+ parent.type === "NullableTypeAnnotation" ||
+ (key === "objectType" &&
+ (parent.type === "IndexedAccessType" ||
+ parent.type === "OptionalIndexedAccessType")) ||
+ parent.type === "TypeOperator"
+ );
// Same as `TSTypeQuery`, but for Flow syntax
case "TypeofTypeAnnotation":
return (
@@ -615,6 +627,7 @@ function needsParens(path, options) {
case "IntersectionTypeAnnotation":
case "UnionTypeAnnotation":
return (
+ parent.type === "TypeOperator" ||
parent.type === "ArrayTypeAnnotation" ||
parent.type === "NullableTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
@@ -632,7 +645,15 @@ function needsParens(path, options) {
parent.type === "OptionalIndexedAccessType"))
);
+ case "ComponentTypeAnnotation":
case "FunctionTypeAnnotation": {
+ if (
+ node.type === "ComponentTypeAnnotation" &&
+ (node.rendersType === null || node.rendersType === undefined)
+ ) {
+ return false;
+ }
+
if (
path.match(
undefined,
@@ -679,8 +700,8 @@ function needsParens(path, options) {
(key === "checkType" && parent.type === "ConditionalTypeAnnotation") ||
(key === "extendsType" &&
parent.type === "ConditionalTypeAnnotation" &&
- node.returnType.type === "InferTypeAnnotation" &&
- node.returnType.typeParameter.bound) ||
+ node.returnType?.type === "InferTypeAnnotation" &&
+ node.returnType?.typeParameter.bound) ||
// We should check ancestor's parent to know whether the parentheses
// are really needed, but since ??T doesn't make sense this check
// will almost never be true.
@@ -992,6 +1013,7 @@ function needsParens(path, options) {
const isStatement = createTypeCheckFunction([
"BlockStatement",
"BreakStatement",
+ "ComponentDeclaration",
"ClassBody",
"ClassDeclaration",
"ClassMethod",
@@ -1000,10 +1022,12 @@ const isStatement = createTypeCheckFunction([
"ClassPrivateProperty",
"ContinueStatement",
"DebuggerStatement",
+ "DeclareComponent",
"DeclareClass",
"DeclareExportAllDeclaration",
"DeclareExportDeclaration",
"DeclareFunction",
+ "DeclareHook",
"DeclareInterface",
"DeclareModule",
"DeclareModuleExports",
@@ -1020,6 +1044,7 @@ const isStatement = createTypeCheckFunction([
"ForOfStatement",
"ForStatement",
"FunctionDeclaration",
+ "HookDeclaration",
"IfStatement",
"ImportDeclaration",
"InterfaceDeclaration",
diff --git a/src/language-js/parse/flow.js b/src/language-js/parse/flow.js
index 9c57f3a23..0664a00cf 100644
--- a/src/language-js/parse/flow.js
+++ b/src/language-js/parse/flow.js
@@ -12,6 +12,8 @@ const parseOptions = {
// all_comments: true,
// `comments` (boolean, default `true`) - attach comments to AST nodes (`leadingComments` and `trailingComments`)
comments: false,
+ // `components` (boolean, default `false`) - enable parsing of Flow component syntax
+ components: true,
// `enums` (boolean, default `false`) - enable parsing of Flow enums
enums: true,
// `esproposal_decorators` (boolean, default `false`) - enable parsing of decorators
diff --git a/src/language-js/print/block.js b/src/language-js/print/block.js
index f659c06e1..1652a87cc 100644
--- a/src/language-js/print/block.js
+++ b/src/language-js/print/block.js
@@ -40,6 +40,8 @@ function printBlock(path, options, print) {
parent.type === "ArrowFunctionExpression" ||
parent.type === "FunctionExpression" ||
parent.type === "FunctionDeclaration" ||
+ parent.type === "ComponentDeclaration" ||
+ parent.type === "HookDeclaration" ||
parent.type === "ObjectMethod" ||
parent.type === "ClassMethod" ||
parent.type === "ClassPrivateMethod" ||
diff --git a/src/language-js/print/component.js b/src/language-js/print/component.js
new file mode 100644
index 000000000..93232f8ae
--- /dev/null
+++ b/src/language-js/print/component.js
@@ -0,0 +1,142 @@
+import {
+ group,
+ hardline,
+ ifBreak,
+ indent,
+ line,
+ softline,
+} from "../../document/builders.js";
+import { printDanglingComments } from "../../main/comments/print.js";
+import getNextNonSpaceNonCommentCharacter from "../../utils/get-next-non-space-non-comment-character.js";
+import { locEnd } from "../loc.js";
+import { isNextLineEmpty, shouldPrintComma } from "../utils/index.js";
+import { printDeclareToken } from "./misc.js";
+
+/**
+ * @typedef {import("../../common/ast-path.js").default} AstPath
+ * @typedef {import("../../document/builders.js").Doc} Doc
+ */
+
+/*
+- "ComponentDeclaration"
+- "DeclareComponent"
+- "ComponentTypeAnnotation"
+*/
+function printComponent(path, options, print) {
+ const { node } = path;
+
+ const parts = [printDeclareToken(path), "component"];
+ if (node.id) {
+ parts.push(" ", print("id"));
+ }
+
+ parts.push(print("typeParameters"));
+
+ const parametersDoc = printComponentParameters(path, print, options);
+ if (node.rendersType) {
+ parts.push(group([parametersDoc, " ", print("rendersType")]));
+ } else {
+ parts.push(group([parametersDoc]));
+ }
+
+ if (node.body) {
+ parts.push(" ", print("body"));
+ }
+
+ if (options.semi && node.type === "DeclareComponent") {
+ parts.push(";");
+ }
+
+ return parts;
+}
+
+function printComponentParameters(path, print, options) {
+ const { node: componentNode } = path;
+ let parameters = componentNode.params;
+ if (componentNode.rest) {
+ parameters = [...parameters, componentNode.rest];
+ }
+
+ if (parameters.length === 0) {
+ return [
+ "(",
+ printDanglingComments(path, options, {
+ filter: (comment) =>
+ getNextNonSpaceNonCommentCharacter(
+ options.originalText,
+ locEnd(comment),
+ ) === ")",
+ }),
+ ")",
+ ];
+ }
+
+ const printed = [];
+ iterateComponentParametersPath(path, (parameterPath, index) => {
+ const isLastParameter = index === parameters.length - 1;
+ if (isLastParameter && componentNode.rest) {
+ printed.push("...");
+ }
+ printed.push(print());
+ if (isLastParameter) {
+ return;
+ }
+ printed.push(",");
+ if (isNextLineEmpty(parameters[index], options)) {
+ printed.push(hardline, hardline);
+ } else {
+ printed.push(line);
+ }
+ });
+
+ return [
+ "(",
+ indent([softline, ...printed]),
+ ifBreak(
+ shouldPrintComma(options, "all") &&
+ !hasRestParameter(componentNode, parameters)
+ ? ","
+ : "",
+ ),
+ softline,
+ ")",
+ ];
+}
+
+function hasRestParameter(componentNode, parameters) {
+ return componentNode.rest || parameters.at(-1)?.type === "RestElement";
+}
+
+function iterateComponentParametersPath(path, iteratee) {
+ const { node } = path;
+ let index = 0;
+ const callback = (childPath) => iteratee(childPath, index++);
+ path.each(callback, "params");
+ if (node.rest) {
+ path.call(callback, "rest");
+ }
+}
+
+function printComponentParameter(path, options, print) {
+ const { node } = path;
+ if (node.shorthand) {
+ return print("local");
+ }
+
+ return [print("name"), " as ", print("local")];
+}
+
+function printComponentTypeParameter(path, options, print) {
+ const { node } = path;
+
+ const printed = [];
+ if (node.name) {
+ printed.push(print("name"), node.optional ? "?: " : ": ");
+ }
+
+ printed.push(print("typeAnnotation"));
+
+ return printed;
+}
+
+export { printComponent, printComponentParameter, printComponentTypeParameter };
diff --git a/src/language-js/print/flow.js b/src/language-js/print/flow.js
index 18ef2e714..043569138 100644
--- a/src/language-js/print/flow.js
+++ b/src/language-js/print/flow.js
@@ -10,11 +10,21 @@ import isFlowKeywordType from "../utils/is-flow-keyword-type.js";
import { printArray } from "./array.js";
import { printBinaryCastExpression } from "./cast-expression.js";
import { printClass } from "./class.js";
+import {
+ printComponent,
+ printComponentParameter,
+ printComponentTypeParameter,
+} from "./component.js";
import {
printEnumBody,
printEnumDeclaration,
printEnumMember,
} from "./enum.js";
+import {
+ printDeclareHook,
+ printHook,
+ printHookTypeAnnotation,
+} from "./hook.js";
import { printInterface } from "./interface.js";
import { printBigInt } from "./literal.js";
import { printFlowMappedTypeProperty } from "./mapped-type.js";
@@ -56,6 +66,20 @@ function printFlow(path, options, print) {
const semi = options.semi ? ";" : "";
switch (node.type) {
+ case "ComponentDeclaration":
+ case "DeclareComponent":
+ case "ComponentTypeAnnotation":
+ return printComponent(path, options, print);
+ case "ComponentParameter":
+ return printComponentParameter(path, options, print);
+ case "ComponentTypeParameter":
+ return printComponentTypeParameter(path, options, print);
+ case "HookDeclaration":
+ return printHook(path, options, print);
+ case "DeclareHook":
+ return printDeclareHook(path, options, print);
+ case "HookTypeAnnotation":
+ return printHookTypeAnnotation(path, options, print);
case "DeclareClass":
return printClass(path, options, print);
case "DeclareFunction":
@@ -256,6 +280,9 @@ function printFlow(path, options, print) {
case "TypePredicate":
return printTypePredicate(path, print);
+ case "TypeOperator":
+ return [node.operator, " ", print("typeAnnotation")];
+
case "TypeParameterDeclaration":
case "TypeParameterInstantiation":
return printTypeParameters(path, options, print, "params");
diff --git a/src/language-js/print/function-parameters.js b/src/language-js/print/function-parameters.js
index fcdef5cde..420bac78a 100644
--- a/src/language-js/print/function-parameters.js
+++ b/src/language-js/print/function-parameters.js
@@ -137,7 +137,10 @@ function printFunctionParameters(
!functionNode.rest;
if (isFlowShorthandWithOneArg) {
- if (options.arrowParens === "always") {
+ if (
+ options.arrowParens === "always" ||
+ functionNode.type === "HookTypeAnnotation"
+ ) {
return ["(", ...printed, ")"];
}
return printed;
diff --git a/src/language-js/print/hook.js b/src/language-js/print/hook.js
new file mode 100644
index 000000000..1e4f55d82
--- /dev/null
+++ b/src/language-js/print/hook.js
@@ -0,0 +1,114 @@
+import { group } from "../../document/builders.js";
+import { printReturnType } from "./function.js";
+import {
+ printFunctionParameters,
+ shouldGroupFunctionParameters,
+} from "./function-parameters.js";
+import { printDeclareToken } from "./misc.js";
+
+/**
+ * @typedef {import("../../common/ast-path.js").default} AstPath
+ * @typedef {import("../../document/builders.js").Doc} Doc
+ */
+
+/*
+- "HookDeclaration"
+*/
+function printHook(path, options, print) {
+ const { node } = path;
+
+ /** @type {Array} */
+ const parts = ["hook"];
+ if (node.id) {
+ parts.push(" ", print("id"));
+ }
+
+ const parametersDoc = printFunctionParameters(
+ path,
+ print,
+ options,
+ false,
+ true,
+ );
+ const returnTypeDoc = printReturnType(path, print);
+ const shouldGroupParameters = shouldGroupFunctionParameters(
+ node,
+ returnTypeDoc,
+ );
+
+ parts.push(
+ group([
+ shouldGroupParameters ? group(parametersDoc) : parametersDoc,
+ returnTypeDoc,
+ ]),
+ node.body ? " " : "",
+ print("body"),
+ );
+
+ return parts;
+}
+
+/*
+- "DeclareHook"
+*/
+function printDeclareHook(path, options, print) {
+ const { node } = path;
+
+ const parts = [printDeclareToken(path), "hook"];
+ if (node.id) {
+ parts.push(" ", print("id"));
+ }
+
+ if (options.semi) {
+ parts.push(";");
+ }
+
+ return parts;
+}
+
+/*
+`HookTypeAnnotation` is ambiguous:
+- `declare hook useFoo(a: B): void;`
+- `var A: hook (a: B) => void;`
+*/
+function isDeclareHookTypeAnnotation(path) {
+ const { node } = path;
+ return (
+ node.type === "HookTypeAnnotation" &&
+ path.getParentNode(2)?.type === "DeclareHook"
+ );
+}
+
+/*
+- "HookTypeAnnotation"
+*/
+function printHookTypeAnnotation(path, options, print) {
+ const { node } = path;
+ const parts = [];
+
+ parts.push(isDeclareHookTypeAnnotation(path) ? "" : "hook ");
+
+ let parametersDoc = printFunctionParameters(
+ path,
+ print,
+ options,
+ /* expandArg */ false,
+ /* printTypeParams */ true,
+ );
+
+ const returnTypeDoc = [];
+ returnTypeDoc.push(
+ isDeclareHookTypeAnnotation(path) ? ": " : " => ",
+ print("returnType"),
+ );
+
+ if (shouldGroupFunctionParameters(node, returnTypeDoc)) {
+ parametersDoc = group(parametersDoc);
+ }
+
+ parts.push(parametersDoc, returnTypeDoc);
+
+ return group(parts);
+}
+
+export { printDeclareHook, printHook, printHookTypeAnnotation };
diff --git a/src/language-js/print/misc.js b/src/language-js/print/misc.js
index e651233af..0ca691331 100644
--- a/src/language-js/print/misc.js
+++ b/src/language-js/print/misc.js
@@ -48,7 +48,9 @@ function printDefiniteToken(path) {
const flowDeclareNodeTypes = new Set([
"DeclareClass",
+ "DeclareComponent",
"DeclareFunction",
+ "DeclareHook",
"DeclareVariable",
"DeclareExportDeclaration",
"DeclareExportAllDeclaration",
diff --git a/src/language-js/print/module.js b/src/language-js/print/module.js
index 5a0c57034..d5495106e 100644
--- a/src/language-js/print/module.js
+++ b/src/language-js/print/module.js
@@ -105,10 +105,14 @@ function printExportDeclaration(path, options, print) {
const shouldOmitSemicolon = createTypeCheckFunction([
"ClassDeclaration",
+ "ComponentDeclaration",
"FunctionDeclaration",
"TSInterfaceDeclaration",
"DeclareClass",
+ "DeclareComponent",
"DeclareFunction",
+ "DeclareHook",
+ "HookDeclaration",
"TSDeclareFunction",
"EnumDeclaration",
]);
diff --git a/src/language-js/print/type-annotation.js b/src/language-js/print/type-annotation.js
index a80b3d8ae..e38530bf9 100644
--- a/src/language-js/print/type-annotation.js
+++ b/src/language-js/print/type-annotation.js
@@ -473,6 +473,18 @@ const getTypeAnnotationFirstToken = (path) => {
) ||
/*
Flow
+ ```js
+ declare hook foo(): void;
+ ^^^^^^^^ `TypeAnnotation`
+ ```
+ */
+ path.match(
+ (node) => node.type === "TypeAnnotation",
+ (node, key) => key === "typeAnnotation" && node.type === "Identifier",
+ (node, key) => key === "id" && node.type === "DeclareHook",
+ ) ||
+ /*
+ Flow
```js
type A = () => infer R extends string;
diff --git a/src/language-js/traverse/visitor-keys.evaluate.js b/src/language-js/traverse/visitor-keys.evaluate.js
index cafbccf73..9e074605c 100644
--- a/src/language-js/traverse/visitor-keys.evaluate.js
+++ b/src/language-js/traverse/visitor-keys.evaluate.js
@@ -85,20 +85,4 @@ const visitorKeys = Object.fromEntries(
]),
);
-for (const type of [
- // Flow, Not supported yet
- "ComponentDeclaration",
- "ComponentParameter",
- "ComponentTypeAnnotation",
- "ComponentTypeParameter",
- "DeclareComponent",
-
- // Flow, Not supported yet
- "HookDeclaration",
- "HookTypeAnnotation",
- "DeclareHook",
-]) {
- delete visitorKeys[type];
-}
-
export default visitorKeys;
diff --git a/tests/config/run-format-test.js b/tests/config/run-format-test.js
index dd38d24f1..7e2acf881 100644
--- a/tests/config/run-format-test.js
+++ b/tests/config/run-format-test.js
@@ -36,6 +36,8 @@ const unstableTests = new Map(
],
["js/no-semi/comments.js", (options) => options.semi === false],
["flow/no-semi/comments.js", (options) => options.semi === false],
+ "flow/hook/declare-hook.js",
+ "flow/hook/hook-type-annotation.js",
"typescript/prettier-ignore/mapped-types.ts",
"typescript/prettier-ignore/issue-14238.ts",
"js/comments/html-like/comment.js",
diff --git a/tests/format/flow/component/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/component/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..f8aa8720d
--- /dev/null
+++ b/tests/format/flow/component/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,468 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`component-declaration.js [babel-flow] format 1`] = `
+"Unexpected token, expected "{" (15:8)
+ 13 | }
+ 14 |
+> 15 | export component MyComponent() {}
+ | ^
+ 16 |
+ 17 | export default component MyComponent() {}
+ 18 |
+Cause: Unexpected token, expected "{" (15:7)"
+`;
+
+exports[`component-declaration.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+component MyComponent() {}
+
+component MyComponent() renders SomeComponent {}
+
+component MyComponent() renders React.Element {}
+
+component MyComponent() {
+ return ;
+}
+
+component MyComponent(a: string, b: number) renders SomeComponent {
+ return ;
+}
+
+export component MyComponent() {}
+
+export default component MyComponent() {}
+
+component MyComponent() {}
+
+component MyComponent() {}
+
+component MyComponent(bar: string) {}
+
+component MyComponent(bar?: string) {}
+
+component MyComponent(bar: string = '') {}
+
+component MyComponent(propBar as bar: string) {}
+
+component MyComponent(propBar as [bar]: $ReadOnlyArray) {}
+
+component MyComponent(propBar as {bar}: $ReadOnly<{bar: string}>) {}
+
+component MyComponent(propBar as {bar, reallllllllllllllllllllllllllllllllllllllyLong}: $ReadOnly<{bar: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+component MyComponent('data-bar' as bar: string) {}
+
+component MyComponent(...restProps: $ReadOnly<{k: string}>) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string}>) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+component MyComponent(bar: string, 'data-baz' as baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+// Attached comment
+component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz' as baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) {}
+
+component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) {}
+
+component MyComponent() /* Trailing comment */ {}
+
+=====================================output=====================================
+component MyComponent() {}
+
+component MyComponent() renders SomeComponent {}
+
+component MyComponent() renders React.Element<
+ typeof SomeComponentLonnnnnnnnnnnnnnnnnnnnnnnnnnnnng,
+> {}
+
+component MyComponent() {
+ return ;
+}
+
+component MyComponent(a: string, b: number) renders SomeComponent {
+ return ;
+}
+
+export component MyComponent() {}
+
+export default component MyComponent() {}
+
+component MyComponent() {}
+
+component MyComponent<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>() {}
+
+component MyComponent(bar: string) {}
+
+component MyComponent(bar?: string) {}
+
+component MyComponent(bar: string = "") {}
+
+component MyComponent(propBar as bar: string) {}
+
+component MyComponent(propBar as [bar]: $ReadOnlyArray) {}
+
+component MyComponent(propBar as { bar }: $ReadOnly<{ bar: string }>) {}
+
+component MyComponent(
+ propBar as {
+ bar,
+ reallllllllllllllllllllllllllllllllllllllyLong,
+ }: $ReadOnly<{
+ bar: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+) {}
+
+component MyComponent("data-bar" as bar: string) {}
+
+component MyComponent(...restProps: $ReadOnly<{ k: string }>) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{ k: string }>) {}
+
+component MyComponent(
+ bar: string,
+ baz: $ReadOnly<{ k: string }>,
+ realllllllllllllllllllyLong: string,
+) {}
+
+component MyComponent(
+ bar: string,
+ baz: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+) {}
+
+component MyComponent(
+ bar: string,
+ "data-baz" as baz: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+) {}
+
+// Attached comment
+component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ "data-baz" as baz: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+ // Trailing comment
+) {}
+
+component MyComponent(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+ // Trailing comment
+) {}
+
+component MyComponent() /* Trailing comment */ {}
+
+================================================================================
+`;
+
+exports[`component-type-annotation.js [babel-flow] format 1`] = `
+"Unexpected token (1:20)
+> 1 | type T = component();
+ | ^
+ 2 |
+ 3 | type T = component() renders SomeComponent;
+ 4 |
+Cause: Unexpected token (1:19)"
+`;
+
+exports[`component-type-annotation.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+type T = component();
+
+type T = component() renders SomeComponent;
+
+type T = component() renders React.Element;
+
+type T = component();
+
+type T = component();
+
+type T = component(bar: string);
+
+type T = component(bar?: string);
+
+type T = component('data-bar': string);
+
+type T = component(...restProps: $ReadOnly<{k: string}>);
+
+type T = component(...$ReadOnly<{k: string}>);
+
+type T = component(bar: string, baz: $ReadOnly<{k: string}>);
+
+type T = component(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string);
+
+// Attached comment
+type T = component(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz': $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) renders SomeComponent;
+
+type T = component(bar: string) | component(baz: $ReadOnly<{k: string}>) | component(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string);
+
+
+function A(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string): component(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string) {}
+=====================================output=====================================
+type T = component();
+
+type T = component() renders SomeComponent;
+
+type T = component() renders React.Element<
+ typeof SomeComponentLonnnnnnnnnnnnnnnnnnnnnnnnnnnnng,
+>;
+
+type T = component();
+
+type T = component<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>();
+
+type T = component(bar: string);
+
+type T = component(bar?: string);
+
+type T = component("data-bar": string);
+
+type T = component(...restProps: $ReadOnly<{ k: string }>);
+
+type T = component(...$ReadOnly<{ k: string }>);
+
+type T = component(bar: string, baz: $ReadOnly<{ k: string }>);
+
+type T = component(
+ bar: string,
+ baz: $ReadOnly<{ k: string }>,
+ realllllllllllllllllllyLong: string,
+);
+
+// Attached comment
+type T = component(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ "data-baz": $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+ // Trailing comment
+) renders SomeComponent;
+
+type T =
+ | component(bar: string)
+ | component(baz: $ReadOnly<{ k: string }>)
+ | component(
+ realllllllllllllllllllyLong: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ );
+
+function A(
+ realllllllllllllllllllyLong: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+): component(
+ realllllllllllllllllllyLong: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+) {}
+
+================================================================================
+`;
+
+exports[`declare-component.js [babel-flow] format 1`] = `
+"Unexpected token (1:9)
+> 1 | declare component MyComponent();
+ | ^
+ 2 |
+ 3 | declare component MyComponent() renders SomeComponent;
+ 4 |
+Cause: Unexpected token (1:8)"
+`;
+
+exports[`declare-component.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+declare component MyComponent();
+
+declare component MyComponent() renders SomeComponent;
+
+declare component MyComponent() renders React.Element;
+
+declare component MyComponent();
+
+declare component MyComponent();
+
+declare component MyComponent(bar: string);
+
+declare component MyComponent(bar?: string);
+
+declare component MyComponent('data-bar': string);
+
+declare component MyComponent(...restProps: $ReadOnly<{k: string}>);
+
+declare component MyComponent(...$ReadOnly<{k: string}>);
+
+declare component MyComponent(bar: string, baz: $ReadOnly<{k: string}>);
+
+declare component MyComponent(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string);
+
+// Attached comment
+declare component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz': $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) renders SomeComponent;
+=====================================output=====================================
+declare component MyComponent();
+
+declare component MyComponent() renders SomeComponent;
+
+declare component MyComponent() renders React.Element<
+ typeof SomeComponentLonnnnnnnnnnnnnnnnnnnnnnnnnnnnng,
+>;
+
+declare component MyComponent();
+
+declare component MyComponent<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>();
+
+declare component MyComponent(bar: string);
+
+declare component MyComponent(bar?: string);
+
+declare component MyComponent("data-bar": string);
+
+declare component MyComponent(...restProps: $ReadOnly<{ k: string }>);
+
+declare component MyComponent(...$ReadOnly<{ k: string }>);
+
+declare component MyComponent(bar: string, baz: $ReadOnly<{ k: string }>);
+
+declare component MyComponent(
+ bar: string,
+ baz: $ReadOnly<{ k: string }>,
+ realllllllllllllllllllyLong: string,
+);
+
+// Attached comment
+declare component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ "data-baz": $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+ // Trailing comment
+) renders SomeComponent;
+
+================================================================================
+`;
diff --git a/tests/format/flow/component/component-declaration.js b/tests/format/flow/component/component-declaration.js
new file mode 100644
index 000000000..29c45e02b
--- /dev/null
+++ b/tests/format/flow/component/component-declaration.js
@@ -0,0 +1,66 @@
+component MyComponent() {}
+
+component MyComponent() renders SomeComponent {}
+
+component MyComponent() renders React.Element {}
+
+component MyComponent() {
+ return ;
+}
+
+component MyComponent(a: string, b: number) renders SomeComponent {
+ return ;
+}
+
+export component MyComponent() {}
+
+export default component MyComponent() {}
+
+component MyComponent() {}
+
+component MyComponent() {}
+
+component MyComponent(bar: string) {}
+
+component MyComponent(bar?: string) {}
+
+component MyComponent(bar: string = '') {}
+
+component MyComponent(propBar as bar: string) {}
+
+component MyComponent(propBar as [bar]: $ReadOnlyArray) {}
+
+component MyComponent(propBar as {bar}: $ReadOnly<{bar: string}>) {}
+
+component MyComponent(propBar as {bar, reallllllllllllllllllllllllllllllllllllllyLong}: $ReadOnly<{bar: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+component MyComponent('data-bar' as bar: string) {}
+
+component MyComponent(...restProps: $ReadOnly<{k: string}>) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string}>) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string) {}
+
+component MyComponent(bar: string, baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+component MyComponent(bar: string, 'data-baz' as baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>) {}
+
+// Attached comment
+component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz' as baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) {}
+
+component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) {}
+
+component MyComponent() /* Trailing comment */ {}
diff --git a/tests/format/flow/component/component-type-annotation.js b/tests/format/flow/component/component-type-annotation.js
new file mode 100644
index 000000000..6f0723b3b
--- /dev/null
+++ b/tests/format/flow/component/component-type-annotation.js
@@ -0,0 +1,50 @@
+type T = component();
+
+type T = component() renders SomeComponent;
+
+type T = component() renders React.Element;
+
+type T = component();
+
+type T = component();
+
+type T = component(bar: string);
+
+type T = component(bar?: string);
+
+type T = component('data-bar': string);
+
+type T = component(...restProps: $ReadOnly<{k: string}>);
+
+type T = component(...$ReadOnly<{k: string}>);
+
+type T = component(bar: string, baz: $ReadOnly<{k: string}>);
+
+type T = component(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string);
+
+// Attached comment
+type T = component(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz': $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+type T = component(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) renders SomeComponent;
+
+type T = component(bar: string) | component(baz: $ReadOnly<{k: string}>) | component(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string);
+
+
+function A(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string): component(realllllllllllllllllllyLong: string, reallllllllllllllllllllllllllllllllllllllyLong: string) {}
\ No newline at end of file
diff --git a/tests/format/flow/component/declare-component.js b/tests/format/flow/component/declare-component.js
new file mode 100644
index 000000000..49d2bb3bb
--- /dev/null
+++ b/tests/format/flow/component/declare-component.js
@@ -0,0 +1,45 @@
+declare component MyComponent();
+
+declare component MyComponent() renders SomeComponent;
+
+declare component MyComponent() renders React.Element;
+
+declare component MyComponent();
+
+declare component MyComponent();
+
+declare component MyComponent(bar: string);
+
+declare component MyComponent(bar?: string);
+
+declare component MyComponent('data-bar': string);
+
+declare component MyComponent(...restProps: $ReadOnly<{k: string}>);
+
+declare component MyComponent(...$ReadOnly<{k: string}>);
+
+declare component MyComponent(bar: string, baz: $ReadOnly<{k: string}>);
+
+declare component MyComponent(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string);
+
+// Attached comment
+declare component MyComponent(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ 'data-baz': $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+);
+
+declare component MyComponent(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) renders SomeComponent;
\ No newline at end of file
diff --git a/tests/format/flow/component/jsfmt.spec.js b/tests/format/flow/component/jsfmt.spec.js
new file mode 100644
index 000000000..5d99681f6
--- /dev/null
+++ b/tests/format/flow/component/jsfmt.spec.js
@@ -0,0 +1,9 @@
+runFormatTest(import.meta, ["flow"], {
+ errors: {
+ "babel-flow": [
+ "component-declaration.js",
+ "component-type-annotation.js",
+ "declare-component.js",
+ ],
+ },
+});
diff --git a/tests/format/flow/hook/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/hook/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000..92e905aba
--- /dev/null
+++ b/tests/format/flow/hook/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,421 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`declare-hook.js [babel-flow] format 1`] = `
+"Unexpected token (1:9)
+> 1 | declare hook useFoo(): void;
+ | ^
+ 2 |
+ 3 | declare hook useFoo(): SomeComponent;
+ 4 |
+Cause: Unexpected token (1:8)"
+`;
+
+exports[`declare-hook.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+declare hook useFoo(): void;
+
+declare hook useFoo(): SomeComponent;
+
+declare hook useFoo(): React.Element;
+
+declare hook useFoo(): void;
+
+declare hook useFoo(): Array;
+
+declare hook useFoo(): void;
+
+declare hook useFoo(bar: string): string;
+
+declare hook useFoo(bar?: string): string;
+
+declare hook useFoo(data: string): string;
+
+declare hook useFoo(...restProps: $ReadOnly<{k: string}>): {k: string};
+
+declare hook useFoo(...$ReadOnly<{k: string}>): {k: string};
+
+declare hook useFoo(bar: string, baz: $ReadOnly<{k: string}>): void;
+
+declare hook useFoo(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string): void;
+
+// Attached comment
+declare hook useFoo(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): void;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): SomeComponent;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): SomeComponent;
+
+=====================================output=====================================
+declare hook useFoo(): void;
+
+declare hook useFoo(): SomeComponent;
+
+declare hook useFoo(): React.Element<
+ typeof SomeComponentLonnnnnnnnnnnnnnnnnnnnnnnnnnnnng,
+>;
+
+declare hook useFoo(): void;
+
+declare hook useFoo<
+ T,
+>(): Array;
+
+declare hook useFoo<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>(): void;
+
+declare hook useFoo(bar: string): string;
+
+declare hook useFoo(bar?: string): string;
+
+declare hook useFoo(data: string): string;
+
+declare hook useFoo(...restProps: $ReadOnly<{ k: string }>): { k: string };
+
+declare hook useFoo(...$ReadOnly<{ k: string }>): { k: string };
+
+declare hook useFoo(bar: string, baz: $ReadOnly<{ k: string }>): void;
+
+declare hook useFoo(
+ bar: string,
+ baz: $ReadOnly<{ k: string }>,
+ realllllllllllllllllllyLong: string,
+): void;
+
+// Attached comment
+declare hook useFoo(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+): // Trailing comment
+void;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+): // Trailing comment
+SomeComponent;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+): // Trailing comment
+SomeComponent;
+
+================================================================================
+`;
+
+exports[`hook-declaration.js [babel-flow] format 1`] = `
+"Unexpected token, expected "{" (5:8)
+ 3 | export default hook useFoo2() {}
+ 4 |
+> 5 | export hook useFoo3() {}
+ | ^
+ 6 |
+ 7 | hook useFoo4(): string {}
+ 8 |
+Cause: Unexpected token, expected "{" (5:7)"
+`;
+
+exports[`hook-declaration.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+hook useFoo1() {}
+
+export default hook useFoo2() {}
+
+export hook useFoo3() {}
+
+hook useFoo4(): string {}
+
+hook useFoo5() {}
+
+hook useFoo6(...foo) {}
+
+hook useFoo7(...rest?: Foo) {}
+
+hook useFoo8(foo, ...bar) {}
+
+hook useFoo9(foo: Foo, ...bar: Bar) {}
+
+hook useFoo10(foo: () => void,): number { return; }
+
+hook useFoo11(o: { f(string): void }) {}
+
+hook useFoo12(foo, ...bar): React.Element {}
+
+hook useFoo13(foo: Array, ...bar): void {}
+
+hook useFoo14<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>(): any {};
+
+=====================================output=====================================
+hook useFoo1() {}
+
+export default hook useFoo2() {}
+
+export hook useFoo3() {}
+
+hook useFoo4(): string {}
+
+hook useFoo5() {}
+
+hook useFoo6(...foo) {}
+
+hook useFoo7(...rest?: Foo) {}
+
+hook useFoo8(foo, ...bar) {}
+
+hook useFoo9(foo: Foo, ...bar: Bar) {}
+
+hook useFoo10(foo: () => void): number {
+ return;
+}
+
+hook useFoo11(o: { f(string): void }) {}
+
+hook useFoo12(
+ foo,
+ ...bar
+): React.Element {}
+
+hook useFoo13(
+ foo: Array,
+ ...bar
+): void {}
+
+hook useFoo14<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>(): any {}
+
+================================================================================
+`;
+
+exports[`hook-type-annotation.js [babel-flow] format 1`] = `
+"Unexpected token (1:25)
+> 1 | type T = hook () => void;
+ | ^
+ 2 |
+ 3 | type T = hook (bar) => SomeComponent;
+ 4 |
+Cause: Unexpected token (1:24)"
+`;
+
+exports[`hook-type-annotation.js format 1`] = `
+====================================options=====================================
+parsers: ["flow"]
+printWidth: 80
+ | printWidth
+=====================================input======================================
+type T = hook () => void;
+
+type T = hook (bar) => SomeComponent;
+
+type T = hook () => React.Element;
+
+type T = hook () => void;
+
+type T = hook () => Array;
+
+type T = hook () => void;
+
+type T = hook (bar: string) => string;
+
+type T = hook (bar?: string) => string;
+
+type T = hook (data: string) => string;
+
+type T = hook (...restProps: $ReadOnly<{k: string}>) => {k: string};
+
+type T = hook (...$ReadOnly<{k: string}>) => {k: string};
+
+type T = hook (bar: string, baz: $ReadOnly<{k: string}>) => void;
+
+type T = hook (bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string) => void;
+
+type Banana = {
+ useFoo: hook (string) => boolean,
+};
+
+interface F2 { m: hook (string) => number }
+
+type f3 = hook (...arg) => void;
+
+type f4 = hook (/* comment */ arg) => void;
+
+type f5 = hook (arg /* comment */) => void;
+
+type f6 = hook (?arg) => void;
+
+class Y {
+ constructor(
+ ideConnectionFactory: hook (child_process$ChildProcess) => FlowIDEConnection =
+ defaultIDEConnectionFactory,
+ ) {
+ }
+}
+
+interface F {
+ ideConnectionFactoryLongLongLong: hook (child_process$ChildProcess) => FlowIDEConnection
+}
+
+type ExtractType = hook (B) => D
+
+// Attached comment
+type T = hook (
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => void;
+
+type T = hook (
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => SomeComponent;
+
+type T = hook (
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => SomeComponent;
+
+=====================================output=====================================
+type T = hook () => void;
+
+type T = hook (bar) => SomeComponent;
+
+type T = hook () => React.Element<
+ typeof SomeComponentLonnnnnnnnnnnnnnnnnnnnnnnnnnnnng,
+>;
+
+type T = hook () => void;
+
+type T = hook <
+ T,
+>() => Array;
+
+type T = hook <
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>() => void;
+
+type T = hook (bar: string) => string;
+
+type T = hook (bar?: string) => string;
+
+type T = hook (data: string) => string;
+
+type T = hook (...restProps: $ReadOnly<{ k: string }>) => { k: string };
+
+type T = hook (...$ReadOnly<{ k: string }>) => { k: string };
+
+type T = hook (bar: string, baz: $ReadOnly<{ k: string }>) => void;
+
+type T = hook (
+ bar: string,
+ baz: $ReadOnly<{ k: string }>,
+ realllllllllllllllllllyLong: string,
+) => void;
+
+type Banana = {
+ useFoo: hook (string) => boolean,
+};
+
+interface F2 {
+ m: hook (string) => number;
+}
+
+type f3 = hook (...arg) => void;
+
+type f4 = hook (/* comment */ arg) => void;
+
+type f5 = hook (arg /* comment */) => void;
+
+type f6 = hook (?arg) => void;
+
+class Y {
+ constructor(
+ ideConnectionFactory: hook (
+ child_process$ChildProcess,
+ ) => FlowIDEConnection = defaultIDEConnectionFactory,
+ ) {}
+}
+
+interface F {
+ ideConnectionFactoryLongLongLong: hook (
+ child_process$ChildProcess,
+ ) => FlowIDEConnection;
+}
+
+type ExtractType = hook (B) => D;
+
+// Attached comment
+type T = hook (
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>,
+) => // Trailing comment
+void;
+
+type T = hook (
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+) => // Trailing comment
+SomeComponent;
+
+type T = hook (
+ ...props: $ReadOnly<{
+ k: string,
+ reallllllllllllllllllllllllllllllllllllllyLong: string,
+ }>
+) => // Trailing comment
+SomeComponent;
+
+================================================================================
+`;
diff --git a/tests/format/flow/hook/declare-hook.js b/tests/format/flow/hook/declare-hook.js
new file mode 100644
index 000000000..8c3dcedc4
--- /dev/null
+++ b/tests/format/flow/hook/declare-hook.js
@@ -0,0 +1,47 @@
+declare hook useFoo(): void;
+
+declare hook useFoo(): SomeComponent;
+
+declare hook useFoo(): React.Element;
+
+declare hook useFoo(): void;
+
+declare hook useFoo(): Array;
+
+declare hook useFoo(): void;
+
+declare hook useFoo(bar: string): string;
+
+declare hook useFoo(bar?: string): string;
+
+declare hook useFoo(data: string): string;
+
+declare hook useFoo(...restProps: $ReadOnly<{k: string}>): {k: string};
+
+declare hook useFoo(...$ReadOnly<{k: string}>): {k: string};
+
+declare hook useFoo(bar: string, baz: $ReadOnly<{k: string}>): void;
+
+declare hook useFoo(bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string): void;
+
+// Attached comment
+declare hook useFoo(
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): void;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): SomeComponent;
+
+declare hook useFoo(
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+): SomeComponent;
diff --git a/tests/format/flow/hook/hook-declaration.js b/tests/format/flow/hook/hook-declaration.js
new file mode 100644
index 000000000..be8b7361e
--- /dev/null
+++ b/tests/format/flow/hook/hook-declaration.js
@@ -0,0 +1,29 @@
+hook useFoo1() {}
+
+export default hook useFoo2() {}
+
+export hook useFoo3() {}
+
+hook useFoo4(): string {}
+
+hook useFoo5() {}
+
+hook useFoo6(...foo) {}
+
+hook useFoo7(...rest?: Foo) {}
+
+hook useFoo8(foo, ...bar) {}
+
+hook useFoo9(foo: Foo, ...bar: Bar) {}
+
+hook useFoo10(foo: () => void,): number { return; }
+
+hook useFoo11(o: { f(string): void }) {}
+
+hook useFoo12(foo, ...bar): React.Element {}
+
+hook useFoo13(foo: Array, ...bar): void {}
+
+hook useFoo14<
+ T: Fooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo,
+>(): any {};
diff --git a/tests/format/flow/hook/hook-type-annotation.js b/tests/format/flow/hook/hook-type-annotation.js
new file mode 100644
index 000000000..f672ac092
--- /dev/null
+++ b/tests/format/flow/hook/hook-type-annotation.js
@@ -0,0 +1,75 @@
+type T = hook () => void;
+
+type T = hook (bar) => SomeComponent;
+
+type T = hook () => React.Element;
+
+type T = hook () => void;
+
+type T = hook () => Array;
+
+type T = hook () => void;
+
+type T = hook (bar: string) => string;
+
+type T = hook (bar?: string) => string;
+
+type T = hook (data: string) => string;
+
+type T = hook (...restProps: $ReadOnly<{k: string}>) => {k: string};
+
+type T = hook (...$ReadOnly<{k: string}>) => {k: string};
+
+type T = hook (bar: string, baz: $ReadOnly<{k: string}>) => void;
+
+type T = hook (bar: string, baz: $ReadOnly<{k: string}>, realllllllllllllllllllyLong: string) => void;
+
+type Banana = {
+ useFoo: hook (string) => boolean,
+};
+
+interface F2 { m: hook (string) => number }
+
+type f3 = hook (...arg) => void;
+
+type f4 = hook (/* comment */ arg) => void;
+
+type f5 = hook (arg /* comment */) => void;
+
+type f6 = hook (?arg) => void;
+
+class Y {
+ constructor(
+ ideConnectionFactory: hook (child_process$ChildProcess) => FlowIDEConnection =
+ defaultIDEConnectionFactory,
+ ) {
+ }
+}
+
+interface F {
+ ideConnectionFactoryLongLongLong: hook (child_process$ChildProcess) => FlowIDEConnection
+}
+
+type ExtractType = hook (B) => D
+
+// Attached comment
+type T = hook (
+ /**
+ * Commet block
+ */
+ bar: string, // Trailing comment
+
+ // preceding comment
+ baz: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => void;
+
+type T = hook (
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => SomeComponent;
+
+type T = hook (
+ ...props: $ReadOnly<{k: string, reallllllllllllllllllllllllllllllllllllllyLong: string}>
+ // Trailing comment
+) => SomeComponent;
diff --git a/tests/format/flow/hook/jsfmt.spec.js b/tests/format/flow/hook/jsfmt.spec.js
new file mode 100644
index 000000000..de0340097
--- /dev/null
+++ b/tests/format/flow/hook/jsfmt.spec.js
@@ -0,0 +1,9 @@
+runFormatTest(import.meta, ["flow"], {
+ errors: {
+ "babel-flow": [
+ "declare-hook.js",
+ "hook-declaration.js",
+ "hook-type-annotation.js",
+ ],
+ },
+});
diff --git a/tests/unit/__snapshots__/visitor-keys.js.snap b/tests/unit/__snapshots__/visitor-keys.js.snap
index c9cdd1616..781d0d210 100644
--- a/tests/unit/__snapshots__/visitor-keys.js.snap
+++ b/tests/unit/__snapshots__/visitor-keys.js.snap
@@ -147,6 +147,27 @@ exports[`visitor keys estree 1`] = `
"decorators",
"variance",
],
+ "ComponentDeclaration": [
+ "id",
+ "params",
+ "body",
+ "typeParameters",
+ "rendersType",
+ ],
+ "ComponentParameter": [
+ "name",
+ "local",
+ ],
+ "ComponentTypeAnnotation": [
+ "params",
+ "rest",
+ "typeParameters",
+ "rendersType",
+ ],
+ "ComponentTypeParameter": [
+ "name",
+ "typeAnnotation",
+ ],
"ConditionalExpression": [
"test",
"consequent",
@@ -171,6 +192,13 @@ exports[`visitor keys estree 1`] = `
"implements",
"body",
],
+ "DeclareComponent": [
+ "id",
+ "params",
+ "rest",
+ "typeParameters",
+ "rendersType",
+ ],
"DeclareEnum": [
"id",
"body",
@@ -187,6 +215,9 @@ exports[`visitor keys estree 1`] = `
"id",
"predicate",
],
+ "DeclareHook": [
+ "id",
+ ],
"DeclareInterface": [
"id",
"typeParameters",
@@ -350,6 +381,19 @@ exports[`visitor keys estree 1`] = `
"id",
"typeParameters",
],
+ "HookDeclaration": [
+ "id",
+ "params",
+ "body",
+ "typeParameters",
+ "returnType",
+ ],
+ "HookTypeAnnotation": [
+ "params",
+ "returnType",
+ "rest",
+ "typeParameters",
+ ],
"Identifier": [
"typeAnnotation",
"decorators",