Skip to content

improve typings for TS with array and minItems maxItems #2272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions packages/quicktype-core/src/attributes/Constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ export const minMaxLengthTypeAttributeKind: TypeAttributeKind<MinMaxConstraint>
"maxLength"
);

export const minMaxItemsTypeAttributeKind: TypeAttributeKind<MinMaxConstraint> = new MinMaxConstraintTypeAttributeKind(
"minMaxItems",
new Set<TypeKind>(["array"]),
"minItems",
"maxItems"
);


export const minMaxContainsTypeAttributeKind: TypeAttributeKind<MinMaxConstraint> = new MinMaxConstraintTypeAttributeKind(
"minMaxItems",
new Set<TypeKind>(["array"]),
"minContains",
"maxContains"
);

function producer(schema: JSONSchema, minProperty: string, maxProperty: string): MinMaxConstraint | undefined {
if (!(typeof schema === "object")) return undefined;

Expand Down Expand Up @@ -151,6 +166,30 @@ export function minMaxLengthAttributeProducer(
return { forString: minMaxLengthTypeAttributeKind.makeAttributes(maybeMinMaxLength) };
}

export function minMaxItemsAttributeProducer(
schema: JSONSchema,
_ref: Ref,
types: Set<JSONSchemaType>
): JSONSchemaAttributes | undefined {
if (!types.has("array")) return undefined;

const maybeMinMaxLength = producer(schema, "minItems", "maxItems");
if (maybeMinMaxLength === undefined) return undefined;
return { forArray: minMaxItemsTypeAttributeKind.makeAttributes(maybeMinMaxLength) };
}

export function minMaxContainsAttributeProducer(
schema: JSONSchema,
_ref: Ref,
types: Set<JSONSchemaType>
): JSONSchemaAttributes | undefined {
if (!types.has("array")) return undefined;

const maybeMinMaxLength = producer(schema, "minContains", "maxContains");
if (maybeMinMaxLength === undefined) return undefined;
return { forArray: minMaxContainsTypeAttributeKind.makeAttributes(maybeMinMaxLength) };
}

export function minMaxValueForType(t: Type): MinMaxConstraint | undefined {
return minMaxTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}
Expand All @@ -159,6 +198,14 @@ export function minMaxLengthForType(t: Type): MinMaxConstraint | undefined {
return minMaxLengthTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export function minMaxItemsForType(t: Type): MinMaxConstraint | undefined {
return minMaxItemsTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export function minMaxContainsForType(t: Type): MinMaxConstraint | undefined {
return minMaxContainsTypeAttributeKind.tryGetInAttributes(t.getAttributes());
}

export class PatternTypeAttributeKind extends TypeAttributeKind<string> {
constructor() {
super("pattern");
Expand Down
29 changes: 19 additions & 10 deletions packages/quicktype-core/src/input/JSONSchemaInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import { accessorNamesAttributeProducer } from "../attributes/AccessorNames";
import { enumValuesAttributeProducer } from "../attributes/EnumValues";
import { minMaxAttributeProducer } from "../attributes/Constraints";
import { minMaxLengthAttributeProducer } from "../attributes/Constraints";
import { minMaxItemsAttributeProducer } from "../attributes/Constraints";
import { minMaxContainsAttributeProducer } from "../attributes/Constraints";
import { patternAttributeProducer } from "../attributes/Constraints";
import { uriSchemaAttributesProducer } from "../attributes/URIAttributes";

Expand Down Expand Up @@ -498,6 +500,7 @@ export type JSONSchemaAttributes = {
forObject?: TypeAttributes;
forNumber?: TypeAttributes;
forString?: TypeAttributes;
forArray?: TypeAttributes;
forCases?: TypeAttributes[];
};
export type JSONSchemaAttributeProducer = (
Expand Down Expand Up @@ -547,7 +550,7 @@ class Resolver {
assert(canonical.hasAddress, "Canonical ref can't be resolved without an address");
const address = canonical.address;

let schema =
const schema =
canonical.addressURI === undefined
? undefined
: await this._store.get(address, this._ctx.debugPrintSchemaResolving);
Expand Down Expand Up @@ -616,7 +619,7 @@ async function addTypesInSchema(
references: ReadonlyMap<string, Ref>,
attributeProducers: JSONSchemaAttributeProducer[]
): Promise<void> {
let typeForCanonicalRef = new EqualityMap<Ref, TypeRef>();
const typeForCanonicalRef = new EqualityMap<Ref, TypeRef>();

function setTypeForLocation(loc: Location, t: TypeRef): void {
const maybeRef = typeForCanonicalRef.get(loc.canonicalRef);
Expand Down Expand Up @@ -783,26 +786,25 @@ async function addTypesInSchema(
}
}

async function makeArrayType(): Promise<TypeRef> {
const singularAttributes = singularizeTypeNames(typeAttributes);
async function makeArrayType(attributes: TypeAttributes): Promise<TypeRef> {
const items = schema.items;
let itemType: TypeRef;
if (Array.isArray(items)) {
const itemsLoc = loc.push("items");
const itemTypes = await arrayMapSync(items, async (item, i) => {
const itemLoc = itemsLoc.push(i.toString());
return await toType(checkJSONSchema(item, itemLoc.canonicalRef), itemLoc, singularAttributes);
return await toType(checkJSONSchema(item, itemLoc.canonicalRef), itemLoc, attributes);
});
itemType = typeBuilder.getUnionType(emptyTypeAttributes, new Set(itemTypes));
itemType = typeBuilder.getUnionType(attributes, new Set(itemTypes));
} else if (typeof items === "object") {
const itemsLoc = loc.push("items");
itemType = await toType(checkJSONSchema(items, itemsLoc.canonicalRef), itemsLoc, singularAttributes);
itemType = await toType(checkJSONSchema(items, itemsLoc.canonicalRef), itemsLoc, attributes);
} else if (items !== undefined) {
return messageError("SchemaArrayItemsMustBeStringOrArray", withRef(loc, { actual: items }));
} else {
itemType = typeBuilder.getPrimitiveType("any");
}
typeBuilder.addAttributes(itemType, singularAttributes);
typeBuilder.addAttributes(itemType, attributes);
return typeBuilder.getArrayType(emptyTypeAttributes, itemType);
}

Expand Down Expand Up @@ -935,7 +937,7 @@ async function addTypesInSchema(
}

const stringAttributes = combineTypeAttributes(
"union",
"union",
inferredAttributes,
combineProducedAttributes(({ forString }) => forString)
);
Expand All @@ -948,7 +950,12 @@ async function addTypesInSchema(
}

if (includeArray) {
unionTypes.push(await makeArrayType());
const arrayAttributes = combineTypeAttributes(
"union",
inferredAttributes,
combineProducedAttributes(({ forArray }) => forArray)
);
unionTypes.push(await makeArrayType(arrayAttributes));
}
if (includeObject) {
unionTypes.push(await makeObjectType());
Expand Down Expand Up @@ -1128,6 +1135,8 @@ export class JSONSchemaInput implements Input<JSONSchemaSourceData> {
uriSchemaAttributesProducer,
minMaxAttributeProducer,
minMaxLengthAttributeProducer,
minMaxItemsAttributeProducer,
minMaxContainsAttributeProducer,
patternAttributeProducer
].concat(additionalAttributeProducers);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/quicktype-core/src/language/TypeScriptFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { defined, panic } from "../support/Support";
import { TargetLanguage } from "../TargetLanguage";
import { RenderContext } from "../Renderer";
import { isES3IdentifierStart } from "./JavaScriptUnicodeMaps";
import { minMaxItemsForType } from "../attributes/Constraints";

export const tsFlowOptions = Object.assign({}, javaScriptOptions, {
justTypes: new BooleanOption("just-types", "Interfaces only", false),
Expand Down Expand Up @@ -121,12 +122,19 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer {
_stringType => singleWord("string"),
arrayType => {
const itemType = this.sourceFor(arrayType.items);
const minMaxItems = minMaxItemsForType(arrayType.items);
if (
(arrayType.items instanceof UnionType && !this._tsFlowOptions.declareUnions) ||
arrayType.items instanceof ArrayType
) {
if (minMaxItems?.[0] && minMaxItems[0] > 0) {
return singleWord(["[", itemType.source, ", ...", itemType.source ,"[]]"]);
}
return singleWord(["Array<", itemType.source, ">"]);
} else {
if (minMaxItems?.[0] && minMaxItems[0] > 0) {
return singleWord(["[",parenIfNeeded(itemType) ,", ...", parenIfNeeded(itemType),"[]]"]);
}
return singleWord([parenIfNeeded(itemType), "[]"]);
}
},
Expand Down
14 changes: 13 additions & 1 deletion packages/quicktype-core/src/language/TypeScriptZod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { legalizeName } from "./JavaScript";
import { Sourcelike } from "../Source";
import { panic } from "../support/Support";
import { ConvenienceRenderer } from "../ConvenienceRenderer";
import { minMaxItemsForType } from "../attributes/Constraints";

export const typeScriptZodOptions = {
justSchema: new BooleanOption("just-schema", "Schema only", false)
Expand Down Expand Up @@ -121,7 +122,18 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer {
_integerType => "z.number()",
_doubleType => "z.number()",
_stringType => "z.string()",
arrayType => ["z.array(", this.typeMapTypeFor(arrayType.items, false), ")"],
arrayType => {
const minMaxItems = minMaxItemsForType(arrayType.items);

const arrayString = ["z.array(", this.typeMapTypeFor(arrayType.items, false), ")"]
if (minMaxItems?.[0]) {
arrayString.push('.min(', minMaxItems[0].toString(10) , ')');
}
if (minMaxItems?.[1]) {
arrayString.push('.max(', minMaxItems[1].toString(10) , ')');
}
return arrayString;
},
_classType => panic("Should already be handled."),
_mapType => ["z.record(z.string(), ", this.typeMapTypeFor(_mapType.values, false), ")"],
_enumType => panic("Should already be handled."),
Expand Down