ts-json-schema-generator
Version:
Generate JSON schema from your Typescript sources
83 lines (71 loc) • 3.1 kB
text/typescript
import ts from "typescript";
import { Context, NodeParser } from "../NodeParser";
import { SubNodeParser } from "../SubNodeParser";
import { BaseType } from "../Type/BaseType";
import { IntersectionType } from "../Type/IntersectionType";
import { PrimitiveType } from "../Type/PrimitiveType";
import { UnionType } from "../Type/UnionType";
import { derefType } from "../Utils/derefType";
import { uniqueTypeArray } from "../Utils/uniqueTypeArray";
import { UndefinedType } from "../Type/UndefinedType";
export class IntersectionNodeParser implements SubNodeParser {
public constructor(private typeChecker: ts.TypeChecker, private childNodeParser: NodeParser) {}
public supportsNode(node: ts.IntersectionTypeNode): boolean {
return node.kind === ts.SyntaxKind.IntersectionType;
}
public createType(node: ts.IntersectionTypeNode, context: Context): BaseType | undefined {
const types = node.types.map((subnode) => this.childNodeParser.createType(subnode, context));
// if any type is undefined (never), an intersection type should be undefined (never)
if (types.filter((t) => t === undefined).length) {
return undefined;
}
return translate(types as BaseType[]);
}
}
/**
* Translates the given intersection type into a union type if necessary so `A & (B | C)` becomes
* `(A & B) | (A & C)`. If no translation is needed then the original intersection type is returned.
*/
export function translate(types: BaseType[]): BaseType | undefined {
types = uniqueTypeArray(types as BaseType[]);
if (types.length == 1) {
return types[0];
}
const unions = types.map((type) => {
const derefed = derefType(type);
return derefed instanceof UnionType ? derefed.getTypes() : [type];
});
const result: BaseType[] = [];
function process(i: number, t: BaseType[] = []) {
for (const type of unions[i]) {
let currentTypes = [...t, type];
if (i < unions.length - 1) {
process(i + 1, currentTypes);
} else {
currentTypes = uniqueTypeArray(currentTypes);
if (currentTypes.some((c) => c instanceof UndefinedType)) {
// never
result.push(new UndefinedType());
} else {
const primitives = currentTypes.filter((c) => c instanceof PrimitiveType);
if (primitives.length === 1) {
result.push(primitives[0]);
} else if (primitives.length > 1) {
// conflict -> ignore
} else if (currentTypes.length === 1) {
result.push(currentTypes[0]);
} else {
result.push(new IntersectionType(currentTypes));
}
}
}
}
}
process(0);
if (result.length === 1) {
return result[0];
} else if (result.length > 1) {
return new UnionType(result);
}
return undefined;
}