diff --git a/changelog_unreleased/typescript/15811.md b/changelog_unreleased/typescript/15811.md new file mode 100644 index 000000000..6019f07b0 --- /dev/null +++ b/changelog_unreleased/typescript/15811.md @@ -0,0 +1,24 @@ +#### Improve conditional type alias layout (#15811 by @seiyab) + + +```ts +// Input +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + +// Prettier stable +type FallbackFlags = Equals< + NonNullableFlag["flags"], + {} +> extends true + ? Dict + : NonNullableFlag["flags"]; + +// Prettier main +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; +``` diff --git a/src/language-js/print/assignment.js b/src/language-js/print/assignment.js index 89fd056e1..a196fb698 100644 --- a/src/language-js/print/assignment.js +++ b/src/language-js/print/assignment.js @@ -25,6 +25,10 @@ import { import { shouldInlineLogicalExpression } from "./binaryish.js"; import { printCallExpression } from "./call-expression.js"; +/** + * @typedef {import("../../common/ast-path.js").default} AstPath + */ + function printAssignment( path, options, @@ -147,7 +151,6 @@ function chooseLayout(path, options, print, leftDoc, rightPropertyName) { if ( isComplexDestructuring(node) || - isComplexTypeAliasParams(node) || hasComplexTypeAnnotation(node) || (isArrowFunctionVariableDeclarator(node) && canBreakLeftDoc) ) { @@ -166,6 +169,10 @@ function chooseLayout(path, options, print, leftDoc, rightPropertyName) { return "break-after-operator"; } + if (isComplexTypeAliasParams(node)) { + return "break-lhs"; + } + if ( !canBreakLeftDoc && (hasShortKey || @@ -194,7 +201,10 @@ function shouldBreakAfterOperator(path, options, print, hasShortKey) { return true; case "TSConditionalType": case "ConditionalTypeAnnotation": - if (!options.experimentalTernaries) { + if ( + !options.experimentalTernaries && + !shouldBreakBeforeConditionalType(rightNode) + ) { break; } return true; @@ -435,6 +445,22 @@ function getTypeArgumentsFromCallExpression(node) { return (node.typeParameters ?? node.typeArguments)?.params; } +function shouldBreakBeforeConditionalType(node) { + function isGeneric(subNode) { + switch (subNode.type) { + case "FunctionTypeAnnotation": + case "GenericTypeAnnotation": + case "TSFunctionType": + case "TSTypeReference": + return Boolean(subNode.typeParameters); + default: + return false; + } + } + + return isGeneric(node.checkType) || isGeneric(node.extendsType); +} + export { isArrowFunctionVariableDeclarator, printAssignment, diff --git a/tests/format/flow/type-alias/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/type-alias/__snapshots__/jsfmt.spec.js.snap index 5e1970629..3c7fdeb57 100644 --- a/tests/format/flow/type-alias/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/flow/type-alias/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,89 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`conditional.js [babel-flow] format 1`] = ` +"Unexpected token (2:43) + 1 | type FallbackFlags = +> 2 | Equals["flags"], {}> extends true + | ^ + 3 | ? Dict + 4 | : NonNullableFlag["flags"]; + 5 | +Cause: Unexpected token (2:42)" +`; + +exports[`conditional.js format 1`] = ` +====================================options===================================== +parsers: ["flow"] +printWidth: 80 + | printWidth +=====================================input====================================== +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + + export type UnPromise> = + Type extends Promise + ? Generic + : empty; + +export type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) + ? true + : false; + +export type _Repeat = + __Repeat extends infer X + ? Cast + : empty + +export type Repeat = + N extends mixed + ? L extends mixed + ? _Repeat + : empty + : empty + +export type Intersect = + U1 extends mixed + ? U2 extends mixed + ? {1: U1, 0: empty}[Equals] + : empty + : empty + +=====================================output===================================== +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + +export type UnPromise> = + Type extends Promise ? Generic : empty; + +export type Equals = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 + ? true + : false; + +export type _Repeat = + __Repeat extends infer X ? Cast : empty; + +export type Repeat = N extends mixed + ? L extends mixed + ? _Repeat + : empty + : empty; + +export type Intersect = U1 extends mixed + ? U2 extends mixed + ? { 1: U1, 0: empty }[Equals] + : empty + : empty; + +================================================================================ +`; + exports[`issue-100857.js format 1`] = ` ====================================options===================================== parsers: ["flow"] diff --git a/tests/format/flow/type-alias/conditional.js b/tests/format/flow/type-alias/conditional.js new file mode 100644 index 000000000..f8e0c1c30 --- /dev/null +++ b/tests/format/flow/type-alias/conditional.js @@ -0,0 +1,34 @@ +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + + export type UnPromise> = + Type extends Promise + ? Generic + : empty; + +export type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) + ? true + : false; + +export type _Repeat = + __Repeat extends infer X + ? Cast + : empty + +export type Repeat = + N extends mixed + ? L extends mixed + ? _Repeat + : empty + : empty + +export type Intersect = + U1 extends mixed + ? U2 extends mixed + ? {1: U1, 0: empty}[Equals] + : empty + : empty diff --git a/tests/format/flow/type-alias/jsfmt.spec.js b/tests/format/flow/type-alias/jsfmt.spec.js index c89f3cc86..d4c880f3c 100644 --- a/tests/format/flow/type-alias/jsfmt.spec.js +++ b/tests/format/flow/type-alias/jsfmt.spec.js @@ -1 +1,5 @@ -run_spec(import.meta, ["flow"]); +run_spec(import.meta, ["flow"], { + errors: { + "babel-flow": ["conditional.js"], + }, +}); diff --git a/tests/format/typescript/type-alias/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/type-alias/__snapshots__/jsfmt.spec.js.snap index 544ad6e72..4d3e792ea 100644 --- a/tests/format/typescript/type-alias/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/typescript/type-alias/__snapshots__/jsfmt.spec.js.snap @@ -1,5 +1,78 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`conditional.ts format 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + +export type UnPromise> = + Type extends Promise + ? Generic + : never; + +export type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) + ? true + : false; + +export type _Repeat = + __Repeat extends infer X + ? Cast + : never + +export type Repeat = + N extends unknown + ? L extends unknown + ? _Repeat + : never + : never + +export type Intersect = + U1 extends unknown + ? U2 extends unknown + ? {1: U1, 0: never}[Equals] + : never + : never + +=====================================output===================================== +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + +export type UnPromise> = + Type extends Promise ? Generic : never; + +export type Equals = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 + ? true + : false; + +export type _Repeat = + __Repeat extends infer X ? Cast : never; + +export type Repeat< + A extends any, + N extends number, + L extends List = [], +> = N extends unknown ? (L extends unknown ? _Repeat : never) : never; + +export type Intersect = U1 extends unknown + ? U2 extends unknown + ? { 1: U1; 0: never }[Equals] + : never + : never; + +================================================================================ +`; + exports[`issue-9874.ts format 1`] = ` ====================================options===================================== parsers: ["typescript"] diff --git a/tests/format/typescript/type-alias/conditional.ts b/tests/format/typescript/type-alias/conditional.ts new file mode 100644 index 000000000..c209d644f --- /dev/null +++ b/tests/format/typescript/type-alias/conditional.ts @@ -0,0 +1,34 @@ +type FallbackFlags = + Equals["flags"], {}> extends true + ? Dict + : NonNullableFlag["flags"]; + +export type UnPromise> = + Type extends Promise + ? Generic + : never; + +export type Equals = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) + ? true + : false; + +export type _Repeat = + __Repeat extends infer X + ? Cast + : never + +export type Repeat = + N extends unknown + ? L extends unknown + ? _Repeat + : never + : never + +export type Intersect = + U1 extends unknown + ? U2 extends unknown + ? {1: U1, 0: never}[Equals] + : never + : never