Skip to content

Commit a2e560e

Browse files
committed
Fix missing parentheses in ConditionalTypeAnnotation (#17196)
1 parent fa95c97 commit a2e560e

9 files changed

Lines changed: 83 additions & 38 deletions

File tree

changelog_unreleased/flow/17196.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#### Fix missing parentheses in `ConditionalTypeAnnotation` (#17196 by @fisker)
2+
3+
<!-- prettier-ignore -->
4+
```jsx
5+
// Input
6+
type T<U> = 'a' | ('b' extends U ? 'c' : empty);
7+
type T<U> = 'a' & ('b' extends U ? 'c' : empty);
8+
9+
// Prettier stable
10+
type T<U> = "a" | "b" extends U ? "c" : empty;
11+
type T<U> = "a" & "b" extends U ? "c" : empty;
12+
13+
// Prettier main
14+
type T<U> = "a" | ("b" extends U ? "c" : empty);
15+
type T<U> = "a" & ("b" extends U ? "c" : empty);
16+
```

src/language-js/comments/handle-comments.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getFunctionParameters,
1616
isCallExpression,
1717
isCallLikeExpression,
18+
isConditionalType,
1819
isIntersectionType,
1920
isLineComment,
2021
isMemberExpression,
@@ -360,17 +361,15 @@ function handleNestedConditionalExpressionComments({
360361

361362
const enclosingIsCond =
362363
enclosingNode?.type === "ConditionalExpression" ||
363-
enclosingNode?.type === "ConditionalTypeAnnotation" ||
364-
enclosingNode?.type === "TSConditionalType";
364+
isConditionalType(enclosingNode);
365365

366366
if (!enclosingIsCond) {
367367
return false;
368368
}
369369

370370
const followingIsCond =
371371
followingNode?.type === "ConditionalExpression" ||
372-
followingNode?.type === "ConditionalTypeAnnotation" ||
373-
followingNode?.type === "TSConditionalType";
372+
isConditionalType(followingNode);
374373

375374
if (followingIsCond) {
376375
addDanglingComment(enclosingNode, comment);
@@ -394,8 +393,7 @@ function handleConditionalExpressionComments({
394393
if (
395394
(!precedingNode || !isSameLineAsPrecedingNode) &&
396395
(enclosingNode?.type === "ConditionalExpression" ||
397-
enclosingNode?.type === "ConditionalTypeAnnotation" ||
398-
enclosingNode?.type === "TSConditionalType") &&
396+
isConditionalType(enclosingNode)) &&
399397
followingNode
400398
) {
401399
if (

src/language-js/needs-parens.js

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ import {
1010
isBinaryCastExpression,
1111
isBitwiseOperator,
1212
isCallExpression,
13+
isConditionalType,
14+
isIntersectionType,
1315
isMemberExpression,
1416
isNullishCoalescing,
1517
isObjectOrRecordExpression,
1618
isObjectProperty,
19+
isUnionType,
1720
shouldFlatten,
1821
startsWithNoLookaheadToken,
1922
} from "./utils/index.js";
@@ -531,11 +534,20 @@ function needsParens(path, options) {
531534
// fallthrough
532535
case "TSConditionalType":
533536
case "TSConstructorType":
534-
if (key === "extendsType" && parent.type === "TSConditionalType") {
535-
if (node.type === "TSConditionalType") {
536-
return true;
537-
}
537+
case "ConditionalTypeAnnotation":
538+
if (
539+
key === "extendsType" &&
540+
isConditionalType(node) &&
541+
parent.type === node.type
542+
) {
543+
return true;
544+
}
545+
546+
if (key === "checkType" && isConditionalType(parent)) {
547+
return true;
548+
}
538549

550+
if (key === "extendsType" && parent.type === "TSConditionalType") {
539551
let { typeAnnotation } = node.returnType || node.typeAnnotation;
540552

541553
if (
@@ -553,15 +565,11 @@ function needsParens(path, options) {
553565
}
554566
}
555567

556-
if (key === "checkType" && parent.type === "TSConditionalType") {
557-
return true;
558-
}
559568
// fallthrough
560569
case "TSUnionType":
561570
case "TSIntersectionType":
562571
if (
563-
(parent.type === "TSUnionType" ||
564-
parent.type === "TSIntersectionType") &&
572+
(isUnionType(parent) || isIntersectionType(parent)) &&
565573
parent.types.length > 1 &&
566574
(!node.types || node.types.length > 1)
567575
) {
@@ -712,19 +720,6 @@ function needsParens(path, options) {
712720
);
713721
}
714722

715-
case "ConditionalTypeAnnotation":
716-
if (
717-
key === "extendsType" &&
718-
parent.type === "ConditionalTypeAnnotation" &&
719-
node.type === "ConditionalTypeAnnotation"
720-
) {
721-
return true;
722-
}
723-
724-
if (key === "checkType" && parent.type === "ConditionalTypeAnnotation") {
725-
return true;
726-
}
727-
728723
// fallthrough
729724
case "OptionalIndexedAccessType":
730725
return key === "objectType" && parent.type === "IndexedAccessType";

src/language-js/print/ternary.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
hasComment,
1919
isBinaryCastExpression,
2020
isCallExpression,
21+
isConditionalType,
2122
isJsxElement,
2223
isLoneShortArgument,
2324
isMemberExpression,
@@ -153,9 +154,7 @@ function printTernary(path, options, print, args) {
153154

154155
const { node } = path;
155156
const isConditionalExpression = node.type === "ConditionalExpression";
156-
const isTSConditional =
157-
node.type === "TSConditionalType" ||
158-
node.type === "ConditionalTypeAnnotation"; // For Flow.
157+
const isTSConditional = isConditionalType(node);
159158
const consequentNodePropertyName = isConditionalExpression
160159
? "consequent"
161160
: "trueType";
@@ -322,8 +321,7 @@ function printTernary(path, options, print, args) {
322321
" ",
323322
"extends",
324323
" ",
325-
node.extendsType.type === "TSConditionalType" ||
326-
node.extendsType.type === "ConditionalTypeAnnotation" ||
324+
isConditionalType(node.extendsType) ||
327325
node.extendsType.type === "TSMappedType"
328326
? print("extendsType")
329327
: group(wrapInParens(print("extendsType"))),

src/language-js/print/type-annotation.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
createTypeCheckFunction,
1616
hasComment,
1717
hasLeadingOwnLineComment,
18+
isConditionalType,
1819
isFlowObjectTypePropertyAFunction,
1920
isObjectType,
2021
isSimpleType,
@@ -174,9 +175,7 @@ function printUnionType(path, options, print) {
174175
// If there's a leading comment, the parent is doing the indentation
175176
const shouldIndent =
176177
parent.type !== "TypeParameterInstantiation" &&
177-
(parent.type !== "TSConditionalType" || !options.experimentalTernaries) &&
178-
(parent.type !== "ConditionalTypeAnnotation" ||
179-
!options.experimentalTernaries) &&
178+
(!isConditionalType(parent) || !options.experimentalTernaries) &&
180179
parent.type !== "TSTypeParameterInstantiation" &&
181180
parent.type !== "GenericTypeAnnotation" &&
182181
parent.type !== "TSTypeReference" &&

src/language-js/utils/index.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,13 +1074,18 @@ const isBinaryCastExpression = createTypeCheckFunction([
10741074
]);
10751075

10761076
const isUnionType = createTypeCheckFunction([
1077-
"UnionTypeAnnotation",
10781077
"TSUnionType",
1078+
"UnionTypeAnnotation",
10791079
]);
10801080

10811081
const isIntersectionType = createTypeCheckFunction([
1082-
"IntersectionTypeAnnotation",
10831082
"TSIntersectionType",
1083+
"IntersectionTypeAnnotation",
1084+
]);
1085+
1086+
const isConditionalType = createTypeCheckFunction([
1087+
"TSConditionalType",
1088+
"ConditionalTypeAnnotation",
10841089
]);
10851090

10861091
export {
@@ -1106,6 +1111,7 @@ export {
11061111
isBitwiseOperator,
11071112
isCallExpression,
11081113
isCallLikeExpression,
1114+
isConditionalType,
11091115
isExportDeclaration,
11101116
isFlowObjectTypePropertyAFunction,
11111117
isFunctionCompositionArgs,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`union.js [babel-flow] format 1`] = `
4+
"Unexpected token, expected ")" (1:24)
5+
> 1 | type T<U> = 'a' | ('b' extends U ? 'c' : empty);
6+
| ^
7+
2 | type T<U> = 'a' & ('b' extends U ? 'c' : empty);
8+
3 |
9+
Cause: Unexpected token, expected ")" (1:23)"
10+
`;
11+
12+
exports[`union.js format 1`] = `
13+
====================================options=====================================
14+
parsers: ["flow", "typescript"]
15+
printWidth: 80
16+
| printWidth
17+
=====================================input======================================
18+
type T<U> = 'a' | ('b' extends U ? 'c' : empty);
19+
type T<U> = 'a' & ('b' extends U ? 'c' : empty);
20+
21+
=====================================output=====================================
22+
type T<U> = "a" | ("b" extends U ? "c" : empty);
23+
type T<U> = "a" & ("b" extends U ? "c" : empty);
24+
25+
================================================================================
26+
`;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
runFormatTest(import.meta, ["flow", "typescript"], {
2+
errors: {
3+
"babel-flow": true,
4+
},
5+
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
type T<U> = 'a' | ('b' extends U ? 'c' : empty);
2+
type T<U> = 'a' & ('b' extends U ? 'c' : empty);

0 commit comments

Comments
 (0)