Skip to content

Commit a85a65b

Browse files
committed
Sort null and undefined last in union types
Resolves #3024
1 parent 8ff7d27 commit a85a65b

File tree

4 files changed

+54
-25
lines changed

4 files changed

+54
-25
lines changed

src/lib/converter/types.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ const unionConverter: TypeConverter<ts.UnionTypeNode, ts.UnionType> = {
11321132
convertType(context, type) {
11331133
const types = type.types.map((type) => convertType(context, type));
11341134
normalizeUnion(types);
1135-
sortLiteralUnion(types);
1135+
sortUnion(types);
11361136

11371137
return new UnionType(types);
11381138
},
@@ -1220,18 +1220,30 @@ function kindToModifier(
12201220
}
12211221
}
12221222

1223-
function sortLiteralUnion(types: SomeType[]) {
1224-
if (
1225-
types.some((t) => t.type !== "literal" || typeof t.value !== "number")
1226-
) {
1223+
function sortUnion(types: SomeType[]) {
1224+
// If every member of the union is a literal numeric type, sort in ascending order
1225+
if (types.every(t => t.type === "literal" && typeof t.value === "number")) {
1226+
types.sort((a, b) => {
1227+
const aLit = a as LiteralType;
1228+
const bLit = b as LiteralType;
1229+
1230+
return (aLit.value as number) - (bLit.value as number);
1231+
});
12271232
return;
12281233
}
12291234

1235+
// #3024 Otherwise, leave the union in the converted order with the exception of null
1236+
// and undefined, which should be sorted last, with null before undefined.
12301237
types.sort((a, b) => {
1231-
const aLit = a as LiteralType;
1232-
const bLit = b as LiteralType;
1238+
const aIsNull = a.type === "literal" && a.value === null;
1239+
const aIsUndef = a.type === "intrinsic" && a.name === "undefined";
1240+
const bIsNull = b.type === "literal" && b.value === null;
1241+
const bIsUndef = b.type === "intrinsic" && b.name === "undefined";
1242+
1243+
const aWeight = aIsNull ? 1 : aIsUndef ? 2 : 0;
1244+
const bWeight = bIsNull ? 1 : bIsUndef ? 2 : 0;
12331245

1234-
return (aLit.value as number) - (bLit.value as number);
1246+
return aWeight - bWeight;
12351247
});
12361248
}
12371249

src/test/converter/function/specs.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -841,19 +841,19 @@
841841
"type": "union",
842842
"types": [
843843
{
844-
"type": "intrinsic",
845-
"name": "undefined"
844+
"type": "reference",
845+
"target": 47,
846+
"name": "T",
847+
"package": "typedoc",
848+
"refersToTypeParameter": true
846849
},
847850
{
848851
"type": "literal",
849852
"value": null
850853
},
851854
{
852-
"type": "reference",
853-
"target": 47,
854-
"name": "T",
855-
"package": "typedoc",
856-
"refersToTypeParameter": true
855+
"type": "intrinsic",
856+
"name": "undefined"
857857
}
858858
]
859859
}
@@ -921,11 +921,11 @@
921921
"types": [
922922
{
923923
"type": "intrinsic",
924-
"name": "undefined"
924+
"name": "boolean"
925925
},
926926
{
927927
"type": "intrinsic",
928-
"name": "boolean"
928+
"name": "undefined"
929929
}
930930
]
931931
}
@@ -1543,19 +1543,19 @@
15431543
"type": "union",
15441544
"types": [
15451545
{
1546-
"type": "intrinsic",
1547-
"name": "undefined"
1546+
"type": "reference",
1547+
"target": 51,
1548+
"name": "T",
1549+
"package": "typedoc",
1550+
"refersToTypeParameter": true
15481551
},
15491552
{
15501553
"type": "literal",
15511554
"value": null
15521555
},
15531556
{
1554-
"type": "reference",
1555-
"target": 51,
1556-
"name": "T",
1557-
"package": "typedoc",
1558-
"refersToTypeParameter": true
1557+
"type": "intrinsic",
1558+
"name": "undefined"
15591559
}
15601560
]
15611561
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class GH3024 {
2+
method(x: string | undefined) {}
3+
method2(x: string | null) {}
4+
method3(x: string | null | undefined) {}
5+
}

src/test/issues.c2.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ describe("Issue Tests", () => {
282282
const project = convert();
283283
const nullableParam = query(project, "nullable").signatures?.[0]
284284
?.parameters?.[0];
285-
equal(nullableParam?.type?.toString(), "null | string");
285+
equal(nullableParam?.type?.toString(), "string | null");
286286

287287
const nonNullableParam = query(project, "nonNullable").signatures?.[0]
288288
?.parameters?.[0];
@@ -2226,4 +2226,16 @@ describe("Issue Tests", () => {
22262226
equal(btn.comment.blockTags[0].tag, "@fires");
22272227
equal(btn.comment.blockTags[0].typeAnnotation, "{CustomEvent<{id: string, source: Element}>}");
22282228
});
2229+
2230+
it("#3024 places null and undefined last in unions converted from types", () => {
2231+
const project = convert();
2232+
const method = querySig(project, "GH3024.method");
2233+
equal(method.parameters?.[0].type?.toString(), "string | undefined");
2234+
2235+
const method2 = querySig(project, "GH3024.method2");
2236+
equal(method2.parameters?.[0].type?.toString(), "string | null");
2237+
2238+
const method3 = querySig(project, "GH3024.method3");
2239+
equal(method3.parameters?.[0].type?.toString(), "string | null | undefined");
2240+
});
22292241
});

0 commit comments

Comments
 (0)