Skip to content

Commit

Permalink
fix: union types of union types in Records
Browse files Browse the repository at this point in the history
  • Loading branch information
domoritz committed Apr 20, 2024
1 parent c7b9199 commit 8a0fe2e
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 35 deletions.
2 changes: 1 addition & 1 deletion src/NodeParser/MappedTypeNodeParser.ts
Expand Up @@ -103,7 +103,7 @@ export class MappedTypeNodeParser implements SubNodeParser {

protected getProperties(node: ts.MappedTypeNode, keyListType: UnionType, context: Context): ObjectProperty[] {
return keyListType
.getTypes()
.getFlattenedTypes(derefType)
.filter((type): type is LiteralType => type instanceof LiteralType)
.map((type) => [type, this.mapKey(node, type, context)])
.filter((value): value is [LiteralType, LiteralType] => value[1] instanceof LiteralType)
Expand Down
29 changes: 14 additions & 15 deletions src/Type/UnionType.ts
@@ -1,7 +1,7 @@
import { BaseType } from "./BaseType.js";
import { uniqueTypeArray } from "../Utils/uniqueTypeArray.js";
import { NeverType } from "./NeverType.js";
import { derefType } from "../Utils/derefType.js";
import { derefAliasedType, isHiddenType } from "../Utils/derefType.js";

export class UnionType extends BaseType {
private readonly types: BaseType[];
Expand Down Expand Up @@ -41,19 +41,18 @@ export class UnionType extends BaseType {
return this.types;
}

public normalize(): BaseType {
if (this.types.length === 0) {
return new NeverType();
} else if (this.types.length === 1) {
return this.types[0];
} else {
const union = new UnionType(this.types.filter((type) => !(derefType(type) instanceof NeverType)));

if (union.getTypes().length > 1) {
return union;
} else {
return union.normalize();
}
}
/**
* Get the types in this union as a flat list.
*/
public getFlattenedTypes(deref: (type: BaseType) => BaseType = derefAliasedType): BaseType[] {
return this.getTypes()
.filter((t) => !isHiddenType(t))
.map(deref)
.flatMap((t) => {
if (t instanceof UnionType) {
return t.getFlattenedTypes(deref);
}
return t;
});
}
}
24 changes: 5 additions & 19 deletions src/TypeFormatter/LiteralUnionTypeFormatter.ts
Expand Up @@ -6,7 +6,6 @@ import { LiteralType, LiteralValue } from "../Type/LiteralType.js";
import { NullType } from "../Type/NullType.js";
import { StringType } from "../Type/StringType.js";
import { UnionType } from "../Type/UnionType.js";
import { derefAliasedType, isHiddenType } from "../Utils/derefType.js";
import { typeName } from "../Utils/typeName.js";
import { uniqueArray } from "../Utils/uniqueArray.js";

Expand All @@ -20,10 +19,10 @@ export class LiteralUnionTypeFormatter implements SubTypeFormatter {
let allStrings = true;
let hasNull = false;

const flattenedTypes = flattenTypes(type);
const literals = type.getFlattenedTypes();

// filter out String types since we need to be more careful about them
const types = flattenedTypes.filter((t) => {
const types = literals.filter((t) => {
if (t instanceof StringType) {
hasString = true;
preserveLiterals = preserveLiterals || t.getPreserveLiterals();
Expand Down Expand Up @@ -70,23 +69,10 @@ export class LiteralUnionTypeFormatter implements SubTypeFormatter {
}
}

function flattenTypes(type: UnionType): (StringType | LiteralType | NullType)[] {
return type
.getTypes()
.filter((t) => !isHiddenType(t))
.map(derefAliasedType)
.flatMap((t) => {
if (t instanceof UnionType) {
return flattenTypes(t);
}
return t as StringType | LiteralType | NullType;
});
}

export function isLiteralUnion(type: UnionType): boolean {
return flattenTypes(type).every(
(item) => item instanceof LiteralType || item instanceof NullType || item instanceof StringType,
);
return type
.getFlattenedTypes()
.every((item) => item instanceof LiteralType || item instanceof NullType || item instanceof StringType);
}

function getLiteralValue(value: LiteralType | NullType): LiteralValue | null {
Expand Down
6 changes: 6 additions & 0 deletions test/valid-data/type-mapped-union/main.ts
@@ -0,0 +1,6 @@
type MyType1 = "s1";
type MyType2 = MyType1 | "s2" | "s3";
type MyType3 = MyType2 | "s4" | "s5";
type MyType10 = MyType3 | MyType2 | "s6";

export type MyType = Record<MyType10, string>;
40 changes: 40 additions & 0 deletions test/valid-data/type-mapped-union/schema.json
@@ -0,0 +1,40 @@
{
"$ref": "#/definitions/MyType",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"MyType": {
"additionalProperties": {
"type": "string"
},
"properties": {
"s1": {
"type": "string"
},
"s2": {
"type": "string"
},
"s3": {
"type": "string"
},
"s4": {
"type": "string"
},
"s5": {
"type": "string"
},
"s6": {
"type": "string"
}
},
"required": [
"s4",
"s5",
"s2",
"s3",
"s1",
"s6"
],
"type": "object"
}
}
}

0 comments on commit 8a0fe2e

Please sign in to comment.