diff --git a/.changeset/happy-kangaroos-shake.md b/.changeset/happy-kangaroos-shake.md new file mode 100644 index 0000000000..0901f0a322 --- /dev/null +++ b/.changeset/happy-kangaroos-shake.md @@ -0,0 +1,5 @@ +--- +'@hey-api/openapi-ts': patch +--- + +feat(typescript): add `topType` option allowing to choose `any` over `unknown` diff --git a/packages/openapi-ts-tests/main/test/3.1.x.test.ts b/packages/openapi-ts-tests/main/test/3.1.x.test.ts index b9c467e489..caef2c08b5 100644 --- a/packages/openapi-ts-tests/main/test/3.1.x.test.ts +++ b/packages/openapi-ts-tests/main/test/3.1.x.test.ts @@ -70,7 +70,20 @@ describe(`OpenAPI ${version}`, () => { input: 'additional-properties-true.json', output: 'additional-properties-true', }), - description: 'allows arbitrary properties on objects', + description: 'allows arbitrary properties on objects (unknown top type)', + }, + { + config: createConfig({ + input: 'additional-properties-true.json', + output: 'additional-properties-true-any', + plugins: [ + { + name: '@hey-api/typescript', + topType: 'any', + }, + ], + }), + description: 'allows arbitrary properties on objects (any top type)', }, { config: createConfig({ diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/index.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/index.ts new file mode 100644 index 0000000000..0339b6e31e --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/index.ts @@ -0,0 +1,3 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export * from './types.gen'; diff --git a/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/types.gen.ts b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/types.gen.ts new file mode 100644 index 0000000000..31ed4dfc73 --- /dev/null +++ b/packages/openapi-ts-tests/main/test/__snapshots__/3.1.x/additional-properties-true-any/types.gen.ts @@ -0,0 +1,23 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Foo = { + foo: string; + [key: string]: any | string; +}; + +export type Bar = Foo & { + [key: string]: any; +}; + +export type Baz = Foo & { + bar: string; + [key: string]: any | string; +}; + +export type Qux = { + [key: string]: any; +}; + +export type ClientOptions = { + baseUrl: `${string}://${string}` | (string & {}); +}; diff --git a/packages/openapi-ts-tests/main/test/openapi-ts.config.ts b/packages/openapi-ts-tests/main/test/openapi-ts.config.ts index 95aa64e587..c91c84a04f 100644 --- a/packages/openapi-ts-tests/main/test/openapi-ts.config.ts +++ b/packages/openapi-ts-tests/main/test/openapi-ts.config.ts @@ -169,6 +169,7 @@ export default defineConfig(() => { // name: '我_responses_{{name}}', // response: '他_response_{{name}}', // }, + topType: 'any', // tree: true, // webhooks: { // name: 'Webby{{name}}Hook', diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/config.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/config.ts index 1ce3c19081..b812d78573 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/config.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/config.ts @@ -12,6 +12,7 @@ export const defaultConfig: HeyApiTypeScriptPlugin['Config'] = { case: 'PascalCase', exportFromIndex: true, style: 'preserve', + topType: 'unknown', tree: false, }, handler, diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts index da8e5997c4..1a930de77c 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/plugin.ts @@ -39,9 +39,7 @@ const arrayTypeToIdentifier = ({ }): ts.TypeNode => { if (!schema.items) { return tsc.typeArrayNode( - tsc.keywordTypeNode({ - keyword: 'unknown', - }), + tsc.keywordTypeNode({ keyword: plugin.config.topType }), ); } @@ -332,7 +330,7 @@ const tupleTypeToIdentifier = ({ if (schema.const && Array.isArray(schema.const)) { itemTypes = schema.const.map((value) => { const expression = tsc.valueToExpression({ value }); - return expression ?? tsc.identifier({ text: 'unknown' }); + return expression ?? tsc.identifier({ text: plugin.config.topType }); }); } else if (schema.items) { for (const item of schema.items) { @@ -432,7 +430,7 @@ const schemaTypeToIdentifier = ({ }); case 'unknown': return tsc.keywordTypeNode({ - keyword: 'unknown', + keyword: plugin.config.topType, }); case 'void': return tsc.keywordTypeNode({ diff --git a/packages/openapi-ts/src/plugins/@hey-api/typescript/types.d.ts b/packages/openapi-ts/src/plugins/@hey-api/typescript/types.d.ts index 965697341e..d376378f2e 100644 --- a/packages/openapi-ts/src/plugins/@hey-api/typescript/types.d.ts +++ b/packages/openapi-ts/src/plugins/@hey-api/typescript/types.d.ts @@ -199,6 +199,16 @@ export type UserConfig = Plugin.Name<'@hey-api/typescript'> & { */ response?: StringName; }; + /** + * The top type to use for untyped or unspecified schema values. + * + * Can be: + * - `unknown` (default): safe top type, you must narrow before use + * - `any`: disables type checking, can be used anywhere + * + * @default 'unknown' + */ + topType?: 'any' | 'unknown'; /** * Configuration for webhook-specific types. * @@ -429,6 +439,12 @@ export type Config = Plugin.Name<'@hey-api/typescript'> & { */ response: StringName; }; + /** + * The top type to use for untyped or unspecified schema values. + * + * @default 'unknown' + */ + topType: 'any' | 'unknown'; /** * Configuration for webhook-specific types. *