diff --git a/docs/advanced.md b/docs/advanced.md index abf469e7..0230994d 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -258,8 +258,7 @@ minItems: 2 ```yaml type: array -items: - type: number +items: false prefixItems: - number - number diff --git a/docs/ja/advanced.md b/docs/ja/advanced.md index 9eb8abb0..33a985a5 100644 --- a/docs/ja/advanced.md +++ b/docs/ja/advanced.md @@ -258,8 +258,7 @@ minItems: 2 ```yaml type: array -items: - type: number +items: false prefixItems: - number - number diff --git a/docs/zh/advanced.md b/docs/zh/advanced.md index 0b06e5a0..9c834a66 100644 --- a/docs/zh/advanced.md +++ b/docs/zh/advanced.md @@ -250,8 +250,7 @@ minItems: 2 ```yaml type: array -items: - type: number +items: false prefixItems: - number - number diff --git a/packages/openapi-typescript/src/transform/schema-object.ts b/packages/openapi-typescript/src/transform/schema-object.ts index 7e74f094..902d946e 100644 --- a/packages/openapi-typescript/src/transform/schema-object.ts +++ b/packages/openapi-typescript/src/transform/schema-object.ts @@ -302,11 +302,50 @@ function transformSchemaObjectCore(schemaObject: SchemaObject, options: Transfor if (schemaObject.type === "array") { // default to `unknown[]` let itemType: ts.TypeNode = UNKNOWN; - // tuple type - if (schemaObject.prefixItems || Array.isArray(schemaObject.items)) { - const prefixItems = schemaObject.prefixItems ?? (schemaObject.items as (SchemaObject | ReferenceObject)[]); + // tuple type using deprecated syntax + if (Array.isArray(schemaObject.items)) { + const prefixItems = schemaObject.items; itemType = ts.factory.createTupleTypeNode(prefixItems.map((item) => transformSchemaObject(item, options))); } + // standard tuple type + else if (schemaObject.prefixItems) { + const prefixItems = schemaObject.prefixItems; + itemType = ts.factory.createTupleTypeNode(prefixItems.map((item) => transformSchemaObject(item, options))); + if (schemaObject.items !== undefined) { + if (schemaObject.items !== false) { + let additionalItemType: ts.TypeNode = UNKNOWN; + if ("type" in schemaObject.items) { + additionalItemType = ts.factory.createArrayTypeNode(transformSchemaObject(schemaObject.items, options)); + } else { + additionalItemType = transformSchemaObject(schemaObject.items, options); + } + itemType = ts.factory.createTupleTypeNode([ + ...(itemType as ts.TupleTypeNode).elements, + ts.factory.createRestTypeNode(additionalItemType), + ]); + } + } else if (schemaObject.unevaluatedItems !== undefined) { + if (schemaObject.unevaluatedItems !== false) { + let additionalItemType: ts.TypeNode = UNKNOWN; + if ("type" in schemaObject.unevaluatedItems) { + additionalItemType = ts.factory.createArrayTypeNode( + transformSchemaObject(schemaObject.unevaluatedItems, options), + ); + } else { + additionalItemType = transformSchemaObject(schemaObject.unevaluatedItems, options); + } + itemType = ts.factory.createTupleTypeNode([ + ...(itemType as ts.TupleTypeNode).elements, + ts.factory.createRestTypeNode(additionalItemType), + ]); + } + } else { + itemType = ts.factory.createTupleTypeNode([ + ...(itemType as ts.TupleTypeNode).elements, + ts.factory.createRestTypeNode(ts.factory.createArrayTypeNode(UNKNOWN)), + ]); + } + } // standard array type else if (schemaObject.items) { if ("type" in schemaObject.items && schemaObject.items.type === "array") { diff --git a/packages/openapi-typescript/src/types.ts b/packages/openapi-typescript/src/types.ts index 73a16397..20acef59 100644 --- a/packages/openapi-typescript/src/types.ts +++ b/packages/openapi-typescript/src/types.ts @@ -483,7 +483,8 @@ export interface IntegerSubtype { export interface ArraySubtype { type: "array" | ["array", "null"]; prefixItems?: (SchemaObject | ReferenceObject)[]; - items?: SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[]; + items?: false | SchemaObject | ReferenceObject | (SchemaObject | ReferenceObject)[]; + unevaluatedItems?: false | SchemaObject | ReferenceObject; minItems?: number; maxItems?: number; enum?: (SchemaObject | ReferenceObject)[]; diff --git a/packages/openapi-typescript/test/transform/schema-object/array.test.ts b/packages/openapi-typescript/test/transform/schema-object/array.test.ts index 59a2fddb..30e99f4c 100644 --- a/packages/openapi-typescript/test/transform/schema-object/array.test.ts +++ b/packages/openapi-typescript/test/transform/schema-object/array.test.ts @@ -35,7 +35,7 @@ describe("transformSchemaObject > array", () => { }, ], [ - "tuple > prefixItems", + "tuple > prefixItems (open tuple using items)", { given: { type: "array", @@ -45,6 +45,72 @@ describe("transformSchemaObject > array", () => { want: `[ number, number, + number, + ...number[] +]`, + // options: DEFAULT_OPTIONS, + }, + ], + [ + "tuple > prefixItems (closed tuple using unevaluatedItems)", + { + given: { + type: "array", + unevaluatedItems: { type: "number" }, + prefixItems: [{ type: "number" }, { type: "number" }, { type: "number" }], + }, + want: `[ + number, + number, + number, + ...number[] +]`, + // options: DEFAULT_OPTIONS, + }, + ], + [ + "tuple > prefixItems (open tuple not specified)", + { + given: { + type: "array", + prefixItems: [{ type: "number" }, { type: "number" }, { type: "number" }], + }, + want: `[ + number, + number, + number, + ...unknown[] +]`, + // options: DEFAULT_OPTIONS, + }, + ], + [ + "tuple > prefixItems (closed tuple using items)", + { + given: { + type: "array", + items: false, + prefixItems: [{ type: "number" }, { type: "number" }, { type: "number" }], + }, + want: `[ + number, + number, + number +]`, + // options: DEFAULT_OPTIONS, + }, + ], + [ + "tuple > prefixItems (closed tuple using unevaluatedItems)", + { + given: { + type: "array", + unevaluatedItems: false, + prefixItems: [{ type: "number" }, { type: "number" }, { type: "number" }], + }, + want: `[ + number, + number, number ]`, // options: DEFAULT_OPTIONS, @@ -179,7 +245,7 @@ describe("transformSchemaObject > array", () => { { given: { type: "array", - items: { type: "number" }, + items: false, prefixItems: [{ type: "number" }, { type: "number" }, { type: "number" }], }, want: `readonly [