ts-json-schema-generator
Version:
Generate JSON schema from your Typescript sources
91 lines (80 loc) • 4.25 kB
text/typescript
import type { Definition } from "../Schema/Definition.js";
import type { SubTypeFormatter } from "../SubTypeFormatter.js";
import { ArrayType } from "../Type/ArrayType.js";
import type { BaseType } from "../Type/BaseType.js";
import { OptionalType } from "../Type/OptionalType.js";
import { RestType } from "../Type/RestType.js";
import { TupleType } from "../Type/TupleType.js";
import type { TypeFormatter } from "../TypeFormatter.js";
import { notNever } from "../Utils/notNever.js";
import { uniqueArray } from "../Utils/uniqueArray.js";
function uniformRestType(type: RestType, check_type: BaseType): boolean {
const inner = type.getType();
return (
(inner instanceof ArrayType && inner.getItem().getId() === check_type.getId()) ||
(inner instanceof TupleType &&
inner.getTypes().every((tuple_type) => {
if (tuple_type instanceof RestType) {
return uniformRestType(tuple_type, check_type);
} else {
return tuple_type?.getId() === check_type.getId();
}
}))
);
}
export class TupleTypeFormatter implements SubTypeFormatter {
public constructor(protected childTypeFormatter: TypeFormatter) {}
public supportsType(type: BaseType): boolean {
return type instanceof TupleType;
}
public getDefinition(type: TupleType): Definition {
const subTypes = type.getTypes().filter(notNever);
const requiredElements = subTypes.filter((t) => !(t instanceof OptionalType) && !(t instanceof RestType));
const optionalElements = subTypes.filter((t): t is OptionalType => t instanceof OptionalType);
// NOTE: A maximum of one rest type is assumed.
const restType = subTypes.find((t): t is RestType => t instanceof RestType);
const firstItemType = requiredElements.length > 0 ? requiredElements[0] : optionalElements[0]?.getType();
// Check whether the tuple is of any of the following forms:
// [A, A, A]
// [A, A, A?]
// [A?, A?]
// [A, A, A, ...A[]],
const isUniformArray =
firstItemType &&
requiredElements.every((item) => item.getId() === firstItemType.getId()) &&
optionalElements.every((item) => item.getType().getId() === firstItemType.getId()) &&
(!restType || uniformRestType(restType, firstItemType));
// If so, generate a simple array with minItems (and possibly maxItems) instead.
if (isUniformArray) {
return {
type: "array",
items: this.childTypeFormatter.getDefinition(firstItemType),
minItems: requiredElements.length,
...(restType ? {} : { maxItems: requiredElements.length + optionalElements.length }),
};
}
const requiredDefinitions = requiredElements.map((item) => this.childTypeFormatter.getDefinition(item));
const optionalDefinitions = optionalElements.map((item) => this.childTypeFormatter.getDefinition(item));
const itemsTotal = requiredDefinitions.length + optionalDefinitions.length;
const additionalItems = restType ? this.childTypeFormatter.getDefinition(restType).items : undefined;
return {
type: "array",
minItems: requiredDefinitions.length,
...(itemsTotal ? { items: requiredDefinitions.concat(optionalDefinitions) } : {}), // with items
...(!itemsTotal && additionalItems ? { items: additionalItems } : {}), // with only rest param
...(!itemsTotal && !additionalItems ? { maxItems: 0 } : {}), // empty
...(additionalItems && !Array.isArray(additionalItems) && itemsTotal
? { additionalItems: additionalItems }
: {}), // with rest items
...(!additionalItems && itemsTotal ? { maxItems: itemsTotal } : {}), // without rest
};
}
public getChildren(type: TupleType): BaseType[] {
return uniqueArray(
type
.getTypes()
.filter(notNever)
.reduce((result: BaseType[], item) => [...result, ...this.childTypeFormatter.getChildren(item)], []),
);
}
}