From b7ab34073e3ef62a31c6014bc97f8ef33f50595b Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Mon, 27 Mar 2023 18:04:35 -0700 Subject: [PATCH] Support conditional type and infer type in Flow (#14573) Co-authored-by: fisker --- changelog_unreleased/flow/14573.md | 17 + package.json | 2 +- src/language-js/needs-parens.js | 21 +- src/language-js/print/flow.js | 6 + src/language-js/print/ternary.js | 4 +- src/language-js/print/type-annotation.js | 29 ++ src/language-js/print/type-parameters.js | 4 + src/language-js/print/typescript.js | 3 +- .../traverse/visitor-keys.evaluate.js | 7 + .../__snapshots__/jsfmt.spec.js.snap | 161 +++++++++ .../conditional-types-comments/comments.js | 73 ++++ .../conditional-types-comments/jsfmt.spec.js | 12 + .../__snapshots__/jsfmt.spec.js.snap | 338 ++++++++++++++++++ .../comments-in-type-annotation.js | 1 + .../conditional-types/conditional-types.js | 27 ++ tests/format/flow/conditional-types/cursor.js | 1 + .../flow/conditional-types/infer-type.js | 9 + .../flow/conditional-types/jsfmt.spec.js | 13 + .../conditional-types/nested-in-condition.js | 19 + .../conditional-types/new-ternary-spec.js | 17 + .../flow/conditional-types/parentheses.js | 4 + .../__snapshots__/jsfmt.spec.js.snap | 27 ++ .../flow/function-type-param/extends-bound.js | 3 + .../flow/function-type-param/jsfmt.spec.js | 6 +- tests/unit/__snapshots__/visitor-keys.js.snap | 9 + yarn.lock | 10 +- 26 files changed, 812 insertions(+), 11 deletions(-) create mode 100644 changelog_unreleased/flow/14573.md create mode 100644 tests/format/flow/conditional-types-comments/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/flow/conditional-types-comments/comments.js create mode 100644 tests/format/flow/conditional-types-comments/jsfmt.spec.js create mode 100644 tests/format/flow/conditional-types/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/flow/conditional-types/comments-in-type-annotation.js create mode 100644 tests/format/flow/conditional-types/conditional-types.js create mode 100644 tests/format/flow/conditional-types/cursor.js create mode 100644 tests/format/flow/conditional-types/infer-type.js create mode 100644 tests/format/flow/conditional-types/jsfmt.spec.js create mode 100644 tests/format/flow/conditional-types/nested-in-condition.js create mode 100644 tests/format/flow/conditional-types/new-ternary-spec.js create mode 100644 tests/format/flow/conditional-types/parentheses.js create mode 100644 tests/format/flow/function-type-param/extends-bound.js diff --git a/changelog_unreleased/flow/14573.md b/changelog_unreleased/flow/14573.md new file mode 100644 index 000000000..f3a6d2cfa --- /dev/null +++ b/changelog_unreleased/flow/14573.md @@ -0,0 +1,17 @@ +#### Support conditional type and infer type (#14573 by @SamChou19815) + + +```jsx +// Input +type TestReturnType any> = T extends (...args: any[]) => infer R ? R : any; + +// Prettier stable +// Does not parse + +// Prettier main +type TestReturnType any> = T extends ( + ...args: any[] +) => infer R + ? R + : any; +``` diff --git a/package.json b/package.json index c3d075e45..f002c2c2e 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "file-entry-cache": "6.0.1", "find-cache-dir": "4.0.0", "find-parent-dir": "0.3.1", - "flow-parser": "0.202.0", + "flow-parser": "0.202.1", "get-stdin": "9.0.0", "graphql": "16.6.0", "hermes-eslint": "0.10.0", diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 57afb21e7..cc3b80b4c 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -559,7 +559,7 @@ function needsParens(path, options) { (parent.type === "IndexedAccessType" || parent.type === "OptionalIndexedAccessType")) ); - + case "InferTypeAnnotation": case "NullableTypeAnnotation": return ( parent.type === "ArrayTypeAnnotation" || @@ -591,6 +591,11 @@ function needsParens(path, options) { (key === "objectType" && (ancestor.type === "IndexedAccessType" || ancestor.type === "OptionalIndexedAccessType")) || + (key === "checkType" && parent.type === "ConditionalTypeAnnotation") || + (key === "extendsType" && + parent.type === "ConditionalTypeAnnotation" && + 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. @@ -604,6 +609,20 @@ function needsParens(path, options) { ); } + case "ConditionalTypeAnnotation": + if ( + key === "extendsType" && + parent.type === "ConditionalTypeAnnotation" && + node.type === "ConditionalTypeAnnotation" + ) { + return true; + } + + if (key === "checkType" && parent.type === "ConditionalTypeAnnotation") { + return true; + } + + // fallthrough case "OptionalIndexedAccessType": return key === "objectType" && parent.type === "IndexedAccessType"; diff --git a/src/language-js/print/flow.js b/src/language-js/print/flow.js index 9b51dd2fd..1cade1bb5 100644 --- a/src/language-js/print/flow.js +++ b/src/language-js/print/flow.js @@ -15,6 +15,7 @@ import { printOpaqueType, printTypeAlias, printIntersectionType, + printInferType, printUnionType, printFunctionType, printIndexedAccessType, @@ -42,6 +43,7 @@ import { printRestSpread, printDeclareToken, } from "./misc.js"; +import { printTernary } from "./ternary.js"; function printFlow(path, options, print) { const { node } = path; @@ -97,6 +99,10 @@ function printFlow(path, options, print) { return printIntersectionType(path, options, print); case "UnionTypeAnnotation": return printUnionType(path, options, print); + case "ConditionalTypeAnnotation": + return printTernary(path, options, print); + case "InferTypeAnnotation": + return printInferType(path, options, print); case "FunctionTypeAnnotation": return printFunctionType(path, options, print); case "TupleTypeAnnotation": diff --git a/src/language-js/print/ternary.js b/src/language-js/print/ternary.js index 5d842de2f..c6993d0f0 100644 --- a/src/language-js/print/ternary.js +++ b/src/language-js/print/ternary.js @@ -182,8 +182,8 @@ function shouldExtraIndentForConditionalExpression(path) { /** * The following is the shared logic for - * ternary operators, namely ConditionalExpression - * and TSConditionalType + * ternary operators, namely ConditionalExpression, + * ConditionalTypeAnnotation and TSConditionalType * @param {AstPath} path - The path to the ConditionalExpression/TSConditionalType node. * @param {Options} options - Prettier options * @param {Function} print - Print function to call recursively diff --git a/src/language-js/print/type-annotation.js b/src/language-js/print/type-annotation.js index 235da0f94..ac35d8d78 100644 --- a/src/language-js/print/type-annotation.js +++ b/src/language-js/print/type-annotation.js @@ -324,6 +324,14 @@ function printIndexedAccessType(path, options, print) { ]; } +/* +- `TSInferType`(TypeScript) +- `InferTypeAnnotation`(flow) +*/ +function printInferType(path, options, print) { + return ["infer ", print("typeParameter")]; +} + // `TSJSDocNullableType`, `TSJSDocNonNullableType` function printJSDocType(path, print, token) { const { node } = path; @@ -367,6 +375,11 @@ function printNamedTupleMember(path, options, print) { ]; } +/* +Normally the `(TS)TypeAnnotation` node starts with `:` token. +If we print `:` in parent node, `cursorNodeDiff` in `/src/main/core.js` will consider `:` is removed, cause cursor moves, see #12491. +Token *before* `(TS)TypeAnnotation.typeAnnotation` should be printed in `getTypeAnnotationFirstToken` function. +*/ const typeAnnotationNodesCheckedLeadingComments = new WeakSet(); function printTypeAnnotationProperty( path, @@ -440,6 +453,21 @@ const getTypeAnnotationFirstToken = (path) => { (node) => node.type === "TypeAnnotation", (node, key) => key === "typeAnnotation" && node.type === "Identifier", (node, key) => key === "id" && node.type === "DeclareFunction" + ) || + /* + Flow + + ```js + type A = () => infer R extends string; + ^^^^^^ `TypeAnnotation` + ``` + */ + path.match( + (node) => node.type === "TypeAnnotation", + (node, key) => + key === "bound" && + node.type === "TypeParameter" && + node.usesExtendsBound ) ) { return ""; @@ -504,6 +532,7 @@ export { printUnionType, printFunctionType, printIndexedAccessType, + printInferType, shouldHugType, printJSDocType, printRestType, diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js index deacd68e5..998e44eb3 100644 --- a/src/language-js/print/type-parameters.js +++ b/src/language-js/print/type-parameters.js @@ -152,6 +152,10 @@ function printTypeParameter(path, options, print) { parts.push(name); if (node.bound) { + if (node.usesExtendsBound) { + parts.push(" extends "); + } + parts.push(printTypeAnnotationProperty(path, print, "bound")); } diff --git a/src/language-js/print/typescript.js b/src/language-js/print/typescript.js index 5aa411cea..5565ec850 100644 --- a/src/language-js/print/typescript.js +++ b/src/language-js/print/typescript.js @@ -47,6 +47,7 @@ import { printUnionType, printFunctionType, printIndexedAccessType, + printInferType, printJSDocType, printRestType, printNamedTupleMember, @@ -372,7 +373,7 @@ function printTypescript(path, options, print) { return printTernary(path, options, print); case "TSInferType": - return ["infer ", print("typeParameter")]; + return printInferType(path, options, print); case "TSIntersectionType": return printIntersectionType(path, options, print); case "TSUnionType": diff --git a/src/language-js/traverse/visitor-keys.evaluate.js b/src/language-js/traverse/visitor-keys.evaluate.js index 98eb504e2..5363df859 100644 --- a/src/language-js/traverse/visitor-keys.evaluate.js +++ b/src/language-js/traverse/visitor-keys.evaluate.js @@ -34,7 +34,14 @@ const additionalVisitorKeys = { QualifiedTypeofIdentifier: ["id", "qualification"], ClassProperty: ["variance"], ClassPrivateProperty: ["variance"], + ConditionalTypeAnnotation: [ + "checkType", + "extendsType", + "trueType", + "falseType", + ], DeclareEnum: flowVisitorKeys.EnumDeclaration, + InferTypeAnnotation: ["typeParameter"], TupleTypeAnnotation: ["elementTypes"], TupleTypeSpreadElement: ["label", "typeAnnotation"], TupleTypeLabeledElement: ["label", "elementType", "variance"], diff --git a/tests/format/flow/conditional-types-comments/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/conditional-types-comments/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..3000371bb --- /dev/null +++ b/tests/format/flow/conditional-types-comments/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,161 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`comments.js [babel-flow] format 1`] = ` +"Unexpected token (3:12) + 1 | // @flow + 2 | +> 3 | type A = B extends T + | ^ + 4 | ? // comment + 5 | foo + 6 | : bar;" +`; + +exports[`comments.js format 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +type A = B extends T + ? // comment + foo + : bar; + +type A = B extends test /* comment + comment + comment +*/ + ? foo + : bar; + +type T = test extends B + ? /* comment + comment + comment + comment + */ + foo + : bar; + +type T = test extends B + ? /* comment + comment + comment + comment + */ + foo + : test extends B + ? /* comment + comment + comment */ + foo + : bar; + +type T = test extends B + ? /* comment */ + foo + : bar; + +type T = test extends B + ? foo + : /* comment + comment + comment + comment + */ + bar; + +type T = test extends B + ? foo + : /* comment + comment + comment + comment + */ + test extends B + ? foo + : /* comment + comment + comment + */ + bar; + +type T = test extends B + ? foo + : /* comment */ + bar; + +type T = test extends B ? test extends B /* c +c */? foo : bar : bar; + +=====================================output===================================== +// @flow + +type A = B extends T // comment + ? foo + : bar; + +type A = B extends test /* comment + comment + comment +*/ + ? foo + : bar; + +type T = test extends B /* comment + comment + comment + comment + */ + ? foo + : bar; + +type T = test extends B /* comment + comment + comment + comment + */ + ? foo + : test extends B /* comment + comment + comment */ + ? foo + : bar; + +type T = test extends B /* comment */ ? foo : bar; + +type T = test extends B + ? foo /* comment + comment + comment + comment + */ + : bar; + +type T = test extends B + ? foo /* comment + comment + comment + comment + */ + : test extends B + ? foo /* comment + comment + comment + */ + : bar; + +type T = test extends B ? foo /* comment */ : bar; + +type T = test extends B + ? test extends B /* c +c */ + ? foo + : bar + : bar; + +================================================================================ +`; diff --git a/tests/format/flow/conditional-types-comments/comments.js b/tests/format/flow/conditional-types-comments/comments.js new file mode 100644 index 000000000..0c4667006 --- /dev/null +++ b/tests/format/flow/conditional-types-comments/comments.js @@ -0,0 +1,73 @@ +// @flow + +type A = B extends T + ? // comment + foo + : bar; + +type A = B extends test /* comment + comment + comment +*/ + ? foo + : bar; + +type T = test extends B + ? /* comment + comment + comment + comment + */ + foo + : bar; + +type T = test extends B + ? /* comment + comment + comment + comment + */ + foo + : test extends B + ? /* comment + comment + comment */ + foo + : bar; + +type T = test extends B + ? /* comment */ + foo + : bar; + +type T = test extends B + ? foo + : /* comment + comment + comment + comment + */ + bar; + +type T = test extends B + ? foo + : /* comment + comment + comment + comment + */ + test extends B + ? foo + : /* comment + comment + comment + */ + bar; + +type T = test extends B + ? foo + : /* comment */ + bar; + +type T = test extends B ? test extends B /* c +c */? foo : bar : bar; diff --git a/tests/format/flow/conditional-types-comments/jsfmt.spec.js b/tests/format/flow/conditional-types-comments/jsfmt.spec.js new file mode 100644 index 000000000..f2ae86bd4 --- /dev/null +++ b/tests/format/flow/conditional-types-comments/jsfmt.spec.js @@ -0,0 +1,12 @@ +run_spec(import.meta, ["flow"], { + errors: { + "babel-flow": [ + "comments.js", + "conditional-types.js", + "infer-type.js", + "nested-in-condition.js", + "new-ternary-spec.js", + "parentheses.js", + ], + }, +}); diff --git a/tests/format/flow/conditional-types/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/conditional-types/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..e4a13acca --- /dev/null +++ b/tests/format/flow/conditional-types/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,338 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`comments-in-type-annotation.js [babel-flow] format 1`] = ` +"Unexpected token (1:24) +> 1 | type A = () => infer R extends/* comment */ string + | ^ + 2 |" +`; + +exports[`comments-in-type-annotation.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = () => infer R extends/* comment */ string + +=====================================output===================================== +type A = () => infer R extends /* comment */ string; + +================================================================================ +`; + +exports[`conditional-types.js [babel-flow] format 1`] = ` +"Unexpected token (3:33) + 1 | // @flow + 2 | +> 3 | export type DeepReadonly = T extends any[] ? DeepReadonlyArray : T extends object ? DeepReadonlyObject : T; + | ^ + 4 | + 5 | interface DeepReadonlyArray extends ReadonlyArray> {} + 6 |" +`; + +exports[`conditional-types.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +export type DeepReadonly = T extends any[] ? DeepReadonlyArray : T extends object ? DeepReadonlyObject : T; + +interface DeepReadonlyArray extends ReadonlyArray> {} + +type TypeName = + T extends string ? "string" : + T extends number ? "number" : + T extends boolean ? "boolean" : + T extends undefined ? "undefined" : + T extends Function ? "function" : + "object"; + +type Type01 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type02 = 0 extends ((1 extends 2 ? 3 : 4)) ? 5 : 6; +type Type03 = 0 extends (((1 extends 2 ? 3 : 4))) ? 5 : 6; +type Type04 = 0 extends ((((1 extends 2 ? 3 : 4)))) ? 5 : 6; +type Type05 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; +type Type06 = ((0 extends 1 ? 2 : 3)) extends 4 ? 5 : 6; +type Type07 = (((0 extends 1 ? 2 : 3))) extends 4 ? 5 : 6; +type Type08 = ((((0 extends 1 ? 2 : 3)))) extends 4 ? 5 : 6; + +type T1 = () => void extends T ? U : V; +type T1a = () => (void extends T ? U : V); +type T1b = () => (void) extends T ? U : V; +type T2 = (() => void) extends T ? U : V; + +=====================================output===================================== +// @flow + +export type DeepReadonly = T extends any[] + ? DeepReadonlyArray + : T extends object + ? DeepReadonlyObject + : T; + +interface DeepReadonlyArray extends ReadonlyArray> {} + +type TypeName = T extends string + ? "string" + : T extends number + ? "number" + : T extends boolean + ? "boolean" + : T extends undefined + ? "undefined" + : T extends Function + ? "function" + : "object"; + +type Type01 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type02 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type03 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type04 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type05 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; +type Type06 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; +type Type07 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; +type Type08 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; + +type T1 = () => void extends T ? U : V; +type T1a = () => void extends T ? U : V; +type T1b = () => void extends T ? U : V; +type T2 = (() => void) extends T ? U : V; + +================================================================================ +`; + +exports[`cursor.js [babel-flow] format 1`] = ` +"Unexpected token (1:24) +> 1 | type A = () => infer R extends string + | ^ + 2 |" +`; + +exports[`cursor.js format 1`] = ` +====================================options===================================== +cursorOffset: 31 +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type A = () => infer R extends <|>string + +=====================================output===================================== +type A = () => infer R extends <|>string; + +================================================================================ +`; + +exports[`infer-type.js [babel-flow] format 1`] = ` +"Unexpected token, expected "," (3:23) + 1 | // @flow + 2 | +> 3 | type TestReturnType any> = T extends (...args: any[]) => infer R ? R : any; + | ^ + 4 | + 5 | type Unpacked = + 6 | T extends (infer U)[] ? U :" +`; + +exports[`infer-type.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +type TestReturnType any> = T extends (...args: any[]) => infer R ? R : any; + +type Unpacked = + T extends (infer U)[] ? U : + T extends (...args: any[]) => infer U ? U : + T extends Promise ? U : + T; + +=====================================output===================================== +// @flow + +type TestReturnType any> = T extends ( + ...args: any[] +) => infer R + ? R + : any; + +type Unpacked = T extends (infer U)[] + ? U + : T extends (...args: any[]) => infer U + ? U + : T extends Promise + ? U + : T; + +================================================================================ +`; + +exports[`nested-in-condition.js [babel-flow] format 1`] = ` +"Unexpected token, expected ")" (4:26) + 2 | + 3 | type Foo = +> 4 | (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) extends + | ^ + 5 | DoubleGobbledygookProvider + 6 | ? UniqueDalgametreService + 7 | : CompositeZamazingoResolver;" +`; + +exports[`nested-in-condition.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +type Foo = + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) extends + DoubleGobbledygookProvider + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo2 = + DoubleGobbledygookProvider extends + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo3 = + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) extends + (DoubleGobbledygookProvider extends MockGobbledygookProvider ? MockThingamabobberFactory : ThingamabobberFactory) + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +=====================================output===================================== +// @flow + +type Foo = ( + ThingamabobberFactory extends AbstractThingamabobberFactory + ? GobbledygookProvider + : CompositeGobbledygookProvider +) extends DoubleGobbledygookProvider + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo2 = DoubleGobbledygookProvider extends ( + ThingamabobberFactory extends AbstractThingamabobberFactory + ? GobbledygookProvider + : CompositeGobbledygookProvider +) + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo3 = ( + ThingamabobberFactory extends AbstractThingamabobberFactory + ? GobbledygookProvider + : CompositeGobbledygookProvider +) extends ( + DoubleGobbledygookProvider extends MockGobbledygookProvider + ? MockThingamabobberFactory + : ThingamabobberFactory +) + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +================================================================================ +`; + +exports[`new-ternary-spec.js [babel-flow] format 1`] = ` +"Unexpected token (5:5) + 3 | // TypeScript examples: + 4 | type TypeName = +> 5 | T extends string ? "string" + | ^ + 6 | : T extends number ? "number" + 7 | : T extends boolean ? "boolean" + 8 | : T extends undefined ? "undefined"" +`; + +exports[`new-ternary-spec.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +// TypeScript examples: +type TypeName = + T extends string ? "string" + : T extends number ? "number" + : T extends boolean ? "boolean" + : T extends undefined ? "undefined" + : T extends Function ? "function" + : "object"; + +type Unpacked = + T extends (infer U)[] ? U + : T extends (...args: any[]) => infer U ? + SomeReallyLongThingThatBreaksTheLine + : T extends Promise ? U + : T; + +=====================================output===================================== +// @flow + +// TypeScript examples: +type TypeName = T extends string + ? "string" + : T extends number + ? "number" + : T extends boolean + ? "boolean" + : T extends undefined + ? "undefined" + : T extends Function + ? "function" + : "object"; + +type Unpacked = T extends (infer U)[] + ? U + : T extends (...args: any[]) => infer U + ? SomeReallyLongThingThatBreaksTheLine + : T extends Promise + ? U + : T; + +================================================================================ +`; + +exports[`parentheses.js [babel-flow] format 1`] = ` +"Unexpected token (4:17) + 2 | + 3 | // #13275 +> 4 | type Foo = T extends ((...a: any[]) => infer R extends string) ? R : never; + | ^ + 5 |" +`; + +exports[`parentheses.js format 1`] = ` +====================================options===================================== +parsers: ["flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +// #13275 +type Foo = T extends ((...a: any[]) => infer R extends string) ? R : never; + +=====================================output===================================== +// @flow + +// #13275 +type Foo = T extends ((...a: any[]) => infer R extends string) ? R : never; + +================================================================================ +`; diff --git a/tests/format/flow/conditional-types/comments-in-type-annotation.js b/tests/format/flow/conditional-types/comments-in-type-annotation.js new file mode 100644 index 000000000..113a4046d --- /dev/null +++ b/tests/format/flow/conditional-types/comments-in-type-annotation.js @@ -0,0 +1 @@ +type A = () => infer R extends/* comment */ string diff --git a/tests/format/flow/conditional-types/conditional-types.js b/tests/format/flow/conditional-types/conditional-types.js new file mode 100644 index 000000000..a7c925a11 --- /dev/null +++ b/tests/format/flow/conditional-types/conditional-types.js @@ -0,0 +1,27 @@ +// @flow + +export type DeepReadonly = T extends any[] ? DeepReadonlyArray : T extends object ? DeepReadonlyObject : T; + +interface DeepReadonlyArray extends ReadonlyArray> {} + +type TypeName = + T extends string ? "string" : + T extends number ? "number" : + T extends boolean ? "boolean" : + T extends undefined ? "undefined" : + T extends Function ? "function" : + "object"; + +type Type01 = 0 extends (1 extends 2 ? 3 : 4) ? 5 : 6; +type Type02 = 0 extends ((1 extends 2 ? 3 : 4)) ? 5 : 6; +type Type03 = 0 extends (((1 extends 2 ? 3 : 4))) ? 5 : 6; +type Type04 = 0 extends ((((1 extends 2 ? 3 : 4)))) ? 5 : 6; +type Type05 = (0 extends 1 ? 2 : 3) extends 4 ? 5 : 6; +type Type06 = ((0 extends 1 ? 2 : 3)) extends 4 ? 5 : 6; +type Type07 = (((0 extends 1 ? 2 : 3))) extends 4 ? 5 : 6; +type Type08 = ((((0 extends 1 ? 2 : 3)))) extends 4 ? 5 : 6; + +type T1 = () => void extends T ? U : V; +type T1a = () => (void extends T ? U : V); +type T1b = () => (void) extends T ? U : V; +type T2 = (() => void) extends T ? U : V; diff --git a/tests/format/flow/conditional-types/cursor.js b/tests/format/flow/conditional-types/cursor.js new file mode 100644 index 000000000..ccf2b9572 --- /dev/null +++ b/tests/format/flow/conditional-types/cursor.js @@ -0,0 +1 @@ +type A = () => infer R extends <|>string diff --git a/tests/format/flow/conditional-types/infer-type.js b/tests/format/flow/conditional-types/infer-type.js new file mode 100644 index 000000000..da0426d01 --- /dev/null +++ b/tests/format/flow/conditional-types/infer-type.js @@ -0,0 +1,9 @@ +// @flow + +type TestReturnType any> = T extends (...args: any[]) => infer R ? R : any; + +type Unpacked = + T extends (infer U)[] ? U : + T extends (...args: any[]) => infer U ? U : + T extends Promise ? U : + T; diff --git a/tests/format/flow/conditional-types/jsfmt.spec.js b/tests/format/flow/conditional-types/jsfmt.spec.js new file mode 100644 index 000000000..ab4d55484 --- /dev/null +++ b/tests/format/flow/conditional-types/jsfmt.spec.js @@ -0,0 +1,13 @@ +run_spec(import.meta, ["flow", "typescript"], { + errors: { + "babel-flow": [ + "comments-in-type-annotation.js", + "conditional-types.js", + "cursor.js", + "infer-type.js", + "nested-in-condition.js", + "new-ternary-spec.js", + "parentheses.js", + ], + }, +}); diff --git a/tests/format/flow/conditional-types/nested-in-condition.js b/tests/format/flow/conditional-types/nested-in-condition.js new file mode 100644 index 000000000..4ac5d720a --- /dev/null +++ b/tests/format/flow/conditional-types/nested-in-condition.js @@ -0,0 +1,19 @@ +// @flow + +type Foo = + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) extends + DoubleGobbledygookProvider + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo2 = + DoubleGobbledygookProvider extends + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) + ? UniqueDalgametreService + : CompositeZamazingoResolver; + +type Foo3 = + (ThingamabobberFactory extends AbstractThingamabobberFactory ? GobbledygookProvider : CompositeGobbledygookProvider) extends + (DoubleGobbledygookProvider extends MockGobbledygookProvider ? MockThingamabobberFactory : ThingamabobberFactory) + ? UniqueDalgametreService + : CompositeZamazingoResolver; diff --git a/tests/format/flow/conditional-types/new-ternary-spec.js b/tests/format/flow/conditional-types/new-ternary-spec.js new file mode 100644 index 000000000..4aec1def7 --- /dev/null +++ b/tests/format/flow/conditional-types/new-ternary-spec.js @@ -0,0 +1,17 @@ +// @flow + +// TypeScript examples: +type TypeName = + T extends string ? "string" + : T extends number ? "number" + : T extends boolean ? "boolean" + : T extends undefined ? "undefined" + : T extends Function ? "function" + : "object"; + +type Unpacked = + T extends (infer U)[] ? U + : T extends (...args: any[]) => infer U ? + SomeReallyLongThingThatBreaksTheLine + : T extends Promise ? U + : T; diff --git a/tests/format/flow/conditional-types/parentheses.js b/tests/format/flow/conditional-types/parentheses.js new file mode 100644 index 000000000..bcf5abeca --- /dev/null +++ b/tests/format/flow/conditional-types/parentheses.js @@ -0,0 +1,4 @@ +// @flow + +// #13275 +type Foo = T extends ((...a: any[]) => infer R extends string) ? R : never; diff --git a/tests/format/flow/function-type-param/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/function-type-param/__snapshots__/jsfmt.spec.js.snap index 277cb0538..d6d305000 100644 --- a/tests/format/flow/function-type-param/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/flow/function-type-param/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,32 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`extends-bound.js [babel-flow] format 1`] = ` +"Unexpected token, expected "," (3:13) + 1 | // @flow + 2 | +> 3 | type F = (t: T) => T; + | ^ + 4 |" +`; + +exports[`extends-bound.js format 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +// @flow + +type F = (t: T) => T; + +=====================================output===================================== +// @flow + +type F = (t: T) => T; + +================================================================================ +`; + exports[`issue-5283.js format 1`] = ` ====================================options===================================== parsers: ["flow"] diff --git a/tests/format/flow/function-type-param/extends-bound.js b/tests/format/flow/function-type-param/extends-bound.js new file mode 100644 index 000000000..755cb6d3f --- /dev/null +++ b/tests/format/flow/function-type-param/extends-bound.js @@ -0,0 +1,3 @@ +// @flow + +type F = (t: T) => T; diff --git a/tests/format/flow/function-type-param/jsfmt.spec.js b/tests/format/flow/function-type-param/jsfmt.spec.js index c89f3cc86..ca803f273 100644 --- a/tests/format/flow/function-type-param/jsfmt.spec.js +++ b/tests/format/flow/function-type-param/jsfmt.spec.js @@ -1 +1,5 @@ -run_spec(import.meta, ["flow"]); +run_spec(import.meta, ["flow"], { + errors: { + "babel-flow": ["extends-bound.js"], + }, +}); diff --git a/tests/unit/__snapshots__/visitor-keys.js.snap b/tests/unit/__snapshots__/visitor-keys.js.snap index 7b25170c7..fdc17281f 100644 --- a/tests/unit/__snapshots__/visitor-keys.js.snap +++ b/tests/unit/__snapshots__/visitor-keys.js.snap @@ -143,6 +143,12 @@ exports[`visitor keys estree 1`] = ` "consequent", "alternate", ], + "ConditionalTypeAnnotation": [ + "checkType", + "extendsType", + "trueType", + "falseType", + ], "ContinueStatement": [ "label", ], @@ -368,6 +374,9 @@ exports[`visitor keys estree 1`] = ` "objectType", "indexType", ], + "InferTypeAnnotation": [ + "typeParameter", + ], "InferredPredicate": [], "InterfaceDeclaration": [ "id", diff --git a/yarn.lock b/yarn.lock index a5b0bb428..ae1c73935 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3988,10 +3988,10 @@ __metadata: languageName: node linkType: hard -"flow-parser@npm:0.202.0": - version: 0.202.0 - resolution: "flow-parser@npm:0.202.0" - checksum: 9212d7cfe002a3ef05d170f022e58b362a1e5633177836d0a9123bcfbaa41a2e7026fff27549aadbb752488d4a05da49809676584da55362f791095795479ec3 +"flow-parser@npm:0.202.1": + version: 0.202.1 + resolution: "flow-parser@npm:0.202.1" + checksum: d2b284c920c3a0d97b29f08111b9214097b52da667d465e375ab693084fedc958cfed30ee4cd51f830a1954d4afa9e0b0d955df37074baada9230cbf61fe05ce languageName: node linkType: hard @@ -6772,7 +6772,7 @@ __metadata: file-entry-cache: 6.0.1 find-cache-dir: 4.0.0 find-parent-dir: 0.3.1 - flow-parser: 0.202.0 + flow-parser: 0.202.1 get-stdin: 9.0.0 graphql: 16.6.0 hermes-eslint: 0.10.0