From 7ceeffd4e6cd47435d5b0b6042256ae175e2af45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rene=CC=81=20Klac=CC=8Can?= Date: Fri, 26 Jun 2026 17:07:43 +0200 Subject: [PATCH] Fix enums --- package-lock.json | 4 +-- src/utils/postProcessModelEnums.spec.ts | 41 ++++++++++++++++++++++ src/utils/postProcessModelEnums.ts | 19 ++++++++-- src/utils/registerHandlebarHelpers.spec.ts | 30 +++++++++++++++- src/utils/registerHandlebarHelpers.ts | 8 +++++ 5 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/utils/postProcessModelEnums.spec.ts diff --git a/package-lock.json b/package-lock.json index 486264be3..0211c68bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openapi-typescript-codegen", - "version": "0.30.0", + "version": "0.31.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openapi-typescript-codegen", - "version": "0.30.0", + "version": "0.31.0", "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^14.2.1", diff --git a/src/utils/postProcessModelEnums.spec.ts b/src/utils/postProcessModelEnums.spec.ts new file mode 100644 index 000000000..10346bd42 --- /dev/null +++ b/src/utils/postProcessModelEnums.spec.ts @@ -0,0 +1,41 @@ +import type { Enum } from '../client/interfaces/Enum'; +import type { Model } from '../client/interfaces/Model'; +import { postProcessModelEnums } from './postProcessModelEnums'; + +const createEnum = (name: string, value: string): Enum => ({ + name, + value, + type: 'string', + description: null, +}); + +const createEnumModel = (name: string, enumValues: Enum[]): Model => ({ + name, + export: 'enum', + type: 'string', + base: 'string', + template: null, + link: null, + description: null, + isDefinition: false, + isReadOnly: false, + isNullable: false, + isRequired: false, + imports: [], + enum: enumValues, + enums: [], + properties: [], +}); + +describe('postProcessModelEnums', () => { + it('merges enum values for duplicate nested enum names', () => { + const gameKind = createEnumModel('kind', [createEnum('GAME', "'game'")]); + const model = createEnumModel('FollowTarget', []); + model.enums = [gameKind, createEnumModel('kind', [createEnum('EVENT', "'event'")]), gameKind]; + + expect(postProcessModelEnums(model)).toEqual([ + createEnumModel('kind', [createEnum('GAME', "'game'"), createEnum('EVENT', "'event'")]), + ]); + expect(gameKind.enum).toEqual([createEnum('GAME', "'game'")]); + }); +}); diff --git a/src/utils/postProcessModelEnums.ts b/src/utils/postProcessModelEnums.ts index 2f06127aa..45d9ff864 100644 --- a/src/utils/postProcessModelEnums.ts +++ b/src/utils/postProcessModelEnums.ts @@ -5,7 +5,20 @@ import type { Model } from '../client/interfaces/Model'; * @param model The model that is post-processed */ export const postProcessModelEnums = (model: Model): Model[] => { - return model.enums.filter((property, index, arr) => { - return arr.findIndex(item => item.name === property.name) === index; - }); + return model.enums.reduce((enums, property) => { + const existing = enums.find(item => item.name === property.name); + if (!existing) { + enums.push({ + ...property, + enum: [...property.enum], + }); + return enums; + } + + existing.enum = existing.enum.concat( + property.enum.filter(enumerator => !existing.enum.some(item => item.name === enumerator.name)) + ); + + return enums; + }, []); }; diff --git a/src/utils/registerHandlebarHelpers.spec.ts b/src/utils/registerHandlebarHelpers.spec.ts index f8347abdb..1acdf637d 100644 --- a/src/utils/registerHandlebarHelpers.spec.ts +++ b/src/utils/registerHandlebarHelpers.spec.ts @@ -4,12 +4,15 @@ import { HttpClient } from '../HttpClient'; import { registerHandlebarHelpers } from './registerHandlebarHelpers'; describe('registerHandlebarHelpers', () => { - it('should register the helpers', () => { + beforeEach(() => { registerHandlebarHelpers({ httpClient: HttpClient.FETCH, useOptions: false, useUnionTypes: false, }); + }); + + it('should register the helpers', () => { const helpers = Object.keys(Handlebars.helpers); expect(helpers).toContain('ifdef'); expect(helpers).toContain('equals'); @@ -22,4 +25,29 @@ describe('registerHandlebarHelpers', () => { expect(helpers).toContain('escapeDescription'); expect(helpers).toContain('camelCase'); }); + + it('should render single-value nested enums as enum members', () => { + const result = Handlebars.helpers.enumerator( + [{ name: 'CURATOR_LIST', value: "'curator_list'", type: 'string', description: null }], + 'PlayerGgFollowTarget', + 'kind', + { fn: (value: string) => value } + ); + + expect(result).toBe('PlayerGgFollowTarget.kind.CURATOR_LIST'); + }); + + it('should render multi-value nested enums as enum types', () => { + const result = Handlebars.helpers.enumerator( + [ + { name: 'GAME', value: "'game'", type: 'string', description: null }, + { name: 'CURATOR_LIST', value: "'curator_list'", type: 'string', description: null }, + ], + 'PlayerGgFollowTarget', + 'kind', + { fn: (value: string) => value } + ); + + expect(result).toBe('PlayerGgFollowTarget.kind'); + }); }); diff --git a/src/utils/registerHandlebarHelpers.ts b/src/utils/registerHandlebarHelpers.ts index 88f47c19b..9c5e0964f 100644 --- a/src/utils/registerHandlebarHelpers.ts +++ b/src/utils/registerHandlebarHelpers.ts @@ -12,6 +12,8 @@ export const registerHandlebarHelpers = (root: { useOptions: boolean; useUnionTypes: boolean; }): void => { + const isValidPropertyAccess = (value: string): boolean => /^[A-Za-z_$][\w$]*$/.test(value); + Handlebars.registerHelper('ifdef', function (this: any, ...args): string { const options = args.pop(); if (!args.every(value => !value)) { @@ -79,6 +81,12 @@ export const registerHandlebarHelpers = (root: { options: Handlebars.HelperOptions ) { if (!root.useUnionTypes && parent && name) { + const uniqueEnumerators = enumerators.filter((enumerator, index, arr) => { + return arr.findIndex(item => item.name === enumerator.name) === index; + }); + if (uniqueEnumerators.length === 1 && isValidPropertyAccess(uniqueEnumerators[0].name)) { + return `${parent}.${name}.${uniqueEnumerators[0].name}`; + } return `${parent}.${name}`; } return options.fn(