Skip to content

Commit 078e7d4

Browse files
improve optional parameter conversion logic for unions
1 parent 1d35007 commit 078e7d4

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

src/convert/function-visitor.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,21 @@ export const functionVisitor = <
134134
}
135135
}
136136
}
137-
if (param.type === "Identifier") {
138-
paramIsOptional =
139-
param.optional ||
140-
(param.typeAnnotation?.type === "TypeAnnotation" &&
141-
param.typeAnnotation?.typeAnnotation.type ===
142-
"NullableTypeAnnotation");
137+
// This next block is somewhat complicated, but basically inspects the parameters to
138+
// determine what combination of optional / null / undefined they should be.
139+
// Flow and TypeScript handle optional parameters differently, and in TS we need to make sure
140+
// an optional parameter is not followed by a non-optional one since in TS optional can be omitted.
141+
if (param.type === "Identifier" && param.typeAnnotation) {
142+
const isNullable =
143+
param.typeAnnotation.type === "TypeAnnotation" &&
144+
param.typeAnnotation.typeAnnotation.type === "NullableTypeAnnotation";
145+
const hasVoid =
146+
param.typeAnnotation.type === "TypeAnnotation" &&
147+
param.typeAnnotation.typeAnnotation.type === "UnionTypeAnnotation" &&
148+
param.typeAnnotation.typeAnnotation.types.some(
149+
(unionType) => unionType.type === "VoidTypeAnnotation"
150+
);
151+
paramIsOptional = param.optional || isNullable || hasVoid;
143152
}
144153

145154
if (!paramIsOptional) {
@@ -177,6 +186,18 @@ export const functionVisitor = <
177186
t.nullLiteralTypeAnnotation(),
178187
t.genericTypeAnnotation(t.identifier("undefined")),
179188
]);
189+
} else if (
190+
identifier.typeAnnotation.typeAnnotation.type ===
191+
"UnionTypeAnnotation"
192+
) {
193+
identifier.typeAnnotation.typeAnnotation.types =
194+
identifier.typeAnnotation.typeAnnotation.types.filter(
195+
(unionType) => unionType.type !== "VoidTypeAnnotation"
196+
);
197+
identifier.typeAnnotation.typeAnnotation = t.unionTypeAnnotation([
198+
identifier.typeAnnotation.typeAnnotation,
199+
t.genericTypeAnnotation(t.identifier("undefined")),
200+
]);
180201
} else {
181202
identifier.typeAnnotation.typeAnnotation = t.unionTypeAnnotation([
182203
identifier.typeAnnotation.typeAnnotation,

src/convert/type-annotations.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,42 @@ describe("transform type annotations", () => {
2626
expect(await transform(src)).toBe(expected);
2727
});
2828

29+
it("Avoids marking void parameters if followed by non-optional", async () => {
30+
const src = `function f(x: void, y: string){};`;
31+
const expected = `function f(x: undefined, y: string){};`;
32+
expect(await transform(src)).toBe(expected);
33+
});
34+
35+
it("Marks void as optional if no other parameters", async () => {
36+
const src = `function f(x: string | void, y?: string){};`;
37+
const expected = `function f(x?: string, y?: string){};`;
38+
expect(await transform(src)).toBe(expected);
39+
});
40+
41+
it("Marks multiple void parameters optional with unions", async () => {
42+
const src = `function f(x: T | void, y: ?string){};`;
43+
const expected = `function f(x?: T, y?: string | null){};`;
44+
expect(await transform(src)).toBe(expected);
45+
});
46+
47+
it("Avoids optionals preceding nullables", async () => {
48+
const src = `function f(x: ?T, y: string){};`;
49+
const expected = `function f(x: T | null | undefined, y: string){};`;
50+
expect(await transform(src)).toBe(expected);
51+
});
52+
53+
it("Avoids optionals preceding non-optional unions", async () => {
54+
const src = `function f(x: T | void, y: string){};`;
55+
const expected = `function f(x: T | undefined, y: string){};`;
56+
expect(await transform(src)).toBe(expected);
57+
});
58+
59+
it("Convertsions optional unions preceding optionals", async () => {
60+
const src = `function f(x: T | void, y?: string){};`;
61+
const expected = `function f(x?: T, y?: string){};`;
62+
expect(await transform(src)).toBe(expected);
63+
});
64+
2965
it("Converts void types to undefined", async () => {
3066
const src = dedent`
3167
const a: string = "";

0 commit comments

Comments
 (0)