From 931c6afc42e5e507bd8aa4d3b8adf0e924b5fe99 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Noblot Date: Sat, 13 May 2023 21:08:54 +0200 Subject: [PATCH 1/3] improve typings for TS with array --- .../src/attributes/Constraints.ts | 47 +++++++++++++++++++ .../src/input/JSONSchemaInput.ts | 25 +++++++--- .../src/language/TypeScriptFlow.ts | 10 +++- .../src/language/TypeScriptZod.ts | 15 +++++- 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/packages/quicktype-core/src/attributes/Constraints.ts b/packages/quicktype-core/src/attributes/Constraints.ts index a0370f4ccf..bfabb143b4 100644 --- a/packages/quicktype-core/src/attributes/Constraints.ts +++ b/packages/quicktype-core/src/attributes/Constraints.ts @@ -110,6 +110,21 @@ export const minMaxLengthTypeAttributeKind: TypeAttributeKind "maxLength" ); +export const minMaxItemsTypeAttributeKind: TypeAttributeKind = new MinMaxConstraintTypeAttributeKind( + "minMaxItems", + new Set(["array"]), + "minItems", + "maxItems" +); + + +export const minMaxContainsTypeAttributeKind: TypeAttributeKind = new MinMaxConstraintTypeAttributeKind( + "minMaxItems", + new Set(["array"]), + "minContains", + "maxContains" +); + function producer(schema: JSONSchema, minProperty: string, maxProperty: string): MinMaxConstraint | undefined { if (!(typeof schema === "object")) return undefined; @@ -151,6 +166,30 @@ export function minMaxLengthAttributeProducer( return { forString: minMaxLengthTypeAttributeKind.makeAttributes(maybeMinMaxLength) }; } +export function minMaxItemsAttributeProducer( + schema: JSONSchema, + _ref: Ref, + types: Set +): 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 +): 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()); } @@ -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 { constructor() { super("pattern"); diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index 61cb97e316..13112e12e1 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -49,6 +49,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"; @@ -494,6 +496,7 @@ export type JSONSchemaAttributes = { forObject?: TypeAttributes; forNumber?: TypeAttributes; forString?: TypeAttributes; + forArray?: TypeAttributes; forCases?: TypeAttributes[]; }; export type JSONSchemaAttributeProducer = ( @@ -779,26 +782,27 @@ async function addTypesInSchema( } } - async function makeArrayType(): Promise { + async function makeArrayType(attributes: TypeAttributes): Promise { const singularAttributes = singularizeTypeNames(typeAttributes); + 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); } @@ -931,7 +935,7 @@ async function addTypesInSchema( } const stringAttributes = combineTypeAttributes( - "union", + "union", inferredAttributes, combineProducedAttributes(({ forString }) => forString) ); @@ -944,7 +948,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()); @@ -1124,6 +1133,8 @@ export class JSONSchemaInput implements Input { uriSchemaAttributesProducer, minMaxAttributeProducer, minMaxLengthAttributeProducer, + minMaxItemsAttributeProducer, + minMaxContainsAttributeProducer, patternAttributeProducer ].concat(additionalAttributeProducers); } diff --git a/packages/quicktype-core/src/language/TypeScriptFlow.ts b/packages/quicktype-core/src/language/TypeScriptFlow.ts index 6f0b725d69..3f9a0185f9 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow.ts @@ -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), @@ -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 ) { - return singleWord(["Array<", itemType.source, ">"]); + if (minMaxItems?.[0] && minMaxItems[0] > 0) { + return singleWord(["[", itemType.source, ", ...", itemType.source ,"[]]"]); + } + return singleWord(["ArrayT<", itemType.source, ">"]); } else { + if (minMaxItems?.[0] && minMaxItems[0] > 0) { + return singleWord(["[",parenIfNeeded(itemType) ,", ...", parenIfNeeded(itemType),"[]]"]); + } return singleWord([parenIfNeeded(itemType), "[]"]); } }, diff --git a/packages/quicktype-core/src/language/TypeScriptZod.ts b/packages/quicktype-core/src/language/TypeScriptZod.ts index 4d4d769e82..24dfb0c343 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod.ts @@ -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) @@ -121,7 +122,19 @@ 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); + console.log('minMax', minMaxItems); + + 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."), From a60d0b20b1f834f0b509bcde85dc48d6c57c0dd0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Noblot Date: Wed, 17 May 2023 21:23:34 +0200 Subject: [PATCH 2/3] rm some error --- packages/quicktype-core/src/input/JSONSchemaInput.ts | 6 ++---- packages/quicktype-core/src/language/TypeScriptZod.ts | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/quicktype-core/src/input/JSONSchemaInput.ts b/packages/quicktype-core/src/input/JSONSchemaInput.ts index 13112e12e1..f6dc4d17da 100644 --- a/packages/quicktype-core/src/input/JSONSchemaInput.ts +++ b/packages/quicktype-core/src/input/JSONSchemaInput.ts @@ -546,7 +546,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); @@ -615,7 +615,7 @@ async function addTypesInSchema( references: ReadonlyMap, attributeProducers: JSONSchemaAttributeProducer[] ): Promise { - let typeForCanonicalRef = new EqualityMap(); + const typeForCanonicalRef = new EqualityMap(); function setTypeForLocation(loc: Location, t: TypeRef): void { const maybeRef = typeForCanonicalRef.get(loc.canonicalRef); @@ -783,8 +783,6 @@ async function addTypesInSchema( } async function makeArrayType(attributes: TypeAttributes): Promise { - const singularAttributes = singularizeTypeNames(typeAttributes); - const items = schema.items; let itemType: TypeRef; if (Array.isArray(items)) { diff --git a/packages/quicktype-core/src/language/TypeScriptZod.ts b/packages/quicktype-core/src/language/TypeScriptZod.ts index 24dfb0c343..aface9103d 100644 --- a/packages/quicktype-core/src/language/TypeScriptZod.ts +++ b/packages/quicktype-core/src/language/TypeScriptZod.ts @@ -124,8 +124,7 @@ export class TypeScriptZodRenderer extends ConvenienceRenderer { _stringType => "z.string()", arrayType => { const minMaxItems = minMaxItemsForType(arrayType.items); - console.log('minMax', minMaxItems); - + const arrayString = ["z.array(", this.typeMapTypeFor(arrayType.items, false), ")"] if (minMaxItems?.[0]) { arrayString.push('.min(', minMaxItems[0].toString(10) , ')'); From 3820de49bfb0e14692bec5e7f58945a98a86b8be Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Noblot Date: Sun, 21 May 2023 14:56:14 +0200 Subject: [PATCH 3/3] fix add typo ArrayT in TypesScriptFlow --- packages/quicktype-core/src/language/TypeScriptFlow.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/quicktype-core/src/language/TypeScriptFlow.ts b/packages/quicktype-core/src/language/TypeScriptFlow.ts index 3f9a0185f9..05fdb30170 100644 --- a/packages/quicktype-core/src/language/TypeScriptFlow.ts +++ b/packages/quicktype-core/src/language/TypeScriptFlow.ts @@ -130,7 +130,7 @@ export abstract class TypeScriptFlowBaseRenderer extends JavaScriptRenderer { if (minMaxItems?.[0] && minMaxItems[0] > 0) { return singleWord(["[", itemType.source, ", ...", itemType.source ,"[]]"]); } - return singleWord(["ArrayT<", itemType.source, ">"]); + return singleWord(["Array<", itemType.source, ">"]); } else { if (minMaxItems?.[0] && minMaxItems[0] > 0) { return singleWord(["[",parenIfNeeded(itemType) ,", ...", parenIfNeeded(itemType),"[]]"]);