Skip to content

Commit 1600f29

Browse files
committed
Fix duplicate type alias generation for enum declarations
1 parent dc59dda commit 1600f29

File tree

4 files changed

+162
-2
lines changed

4 files changed

+162
-2
lines changed

packages/create-schemas/src/plugins/types-plugin.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,26 @@ export function typesPlugin(): Plugin {
1919

2020
const ast = stringToAST(code) as ts.Node[];
2121

22+
// Collect names of all enum declarations to avoid generating duplicate type aliases
23+
const existingEnums = new Set(
24+
ast
25+
.filter(node => ts.isEnumDeclaration(node))
26+
.map(node => (node as ts.EnumDeclaration).name.text)
27+
);
28+
2229
const componentsDeclaration = ast.find(isComponentsInterfaceDeclaration);
2330

2431
assert(componentsDeclaration, "Missing components declaration");
2532
const schema = componentsDeclaration.members.find(isComponentsSchema);
2633
assert(schema, "Missing components declaration");
2734
assert(schema.type && ts.isTypeLiteralNode(schema.type), "Invalid schema type");
28-
35+
2936
const typeNodes = schema.type.members
3037
.map(member => member.name)
3138
.filter(name => name !== undefined)
3239
.filter(name => ts.isStringLiteral(name) || ts.isIdentifier(name))
3340
.map(name => name.text)
41+
.filter(name => !existingEnums.has(toSafeName(name)))
3442
.map(name => {
3543
if (RESERVED_IDENTIFIERS.has(name)) {
3644
throw new Error(`Invalid schema name: ${name}`);

packages/create-schemas/tests/__snapshots__/e2e.test.ts.snap

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,80 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3+
exports[`e2e > enums.yaml / enum generation without duplicate type aliases 1`] = `
4+
"/** This file has been generated by @workleap/create-schemas (https://github.com/workleap/wl-openapi-typescript). Do not modify manually. */
5+
export interface paths {
6+
"/test": {
7+
parameters: {
8+
query?: never;
9+
header?: never;
10+
path?: never;
11+
cookie?: never;
12+
};
13+
get: {
14+
parameters: {
15+
query?: never;
16+
header?: never;
17+
path?: never;
18+
cookie?: never;
19+
};
20+
requestBody?: never;
21+
responses: {
22+
/** @description Success */
23+
200: {
24+
headers: {
25+
[name: string]: unknown;
26+
};
27+
content?: never;
28+
};
29+
};
30+
};
31+
put?: never;
32+
post?: never;
33+
delete?: never;
34+
options?: never;
35+
head?: never;
36+
patch?: never;
37+
trace?: never;
38+
};
39+
}
40+
export type webhooks = Record<string, never>;
41+
export interface components {
42+
schemas: {
43+
/** @enum {string} */
44+
Status: Status;
45+
/** @enum {string} */
46+
Priority: Priority;
47+
Task: {
48+
id?: string;
49+
status?: components["schemas"]["Status"];
50+
priority?: components["schemas"]["Priority"];
51+
name?: string;
52+
};
53+
};
54+
responses: never;
55+
parameters: never;
56+
requestBodies: never;
57+
headers: never;
58+
pathItems: never;
59+
}
60+
export type $defs = Record<string, never>;
61+
export enum Status {
62+
active = "active",
63+
inactive = "inactive",
64+
pending = "pending"
65+
}
66+
export enum Priority {
67+
low = "low",
68+
medium = "medium",
69+
high = "high"
70+
}
71+
export type operations = Record<string, never>;
72+
73+
export type Task = components["schemas"]["Task"];
74+
export type Endpoints = keyof paths;
75+
"
76+
`;
77+
378
exports[`e2e > officevice.yaml / file URLs 1`] = `
479
"/** This file has been generated by @workleap/create-schemas (https://github.com/workleap/wl-openapi-typescript). Do not modify manually. */
580
export interface paths {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
openapi: 3.0.0
2+
info:
3+
title: Enum Test API
4+
version: 1.0.0
5+
paths:
6+
/test:
7+
get:
8+
responses:
9+
'200':
10+
description: Success
11+
components:
12+
schemas:
13+
Status:
14+
type: string
15+
enum:
16+
- active
17+
- inactive
18+
- pending
19+
Priority:
20+
type: string
21+
enum:
22+
- low
23+
- medium
24+
- high
25+
Task:
26+
type: object
27+
properties:
28+
id:
29+
type: string
30+
status:
31+
$ref: '#/components/schemas/Status'
32+
priority:
33+
$ref: '#/components/schemas/Priority'
34+
name:
35+
type: string

packages/create-schemas/tests/e2e.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ describe.concurrent("e2e", () => {
105105

106106
const configFile = `
107107
import { experimental_openapiFetchPlugin } from "../../../src/plugins";
108-
108+
109109
export default { plugins: [experimental_openapiFetchPlugin()] };
110110
`;
111111

@@ -124,4 +124,46 @@ describe.concurrent("e2e", () => {
124124
},
125125
timeout
126126
);
127+
128+
test(
129+
"enums.yaml / enum generation without duplicate type aliases",
130+
async ({ expect, onTestFinished }) => {
131+
const tempFolder = await createTemporaryFolder({ onTestFinished });
132+
133+
const configFile = `
134+
export default {
135+
openApiTsOptions: {
136+
enum: true
137+
}
138+
};
139+
`;
140+
141+
await writeFile(join(tempFolder, "create-schemas.config.ts"), configFile);
142+
143+
const result = await runCompiledBin({
144+
source: join(dataFolder, "enums.yaml"),
145+
outdir: join(tempFolder, "dist"),
146+
cwd: tempFolder
147+
});
148+
149+
const typesFile = result.find(file => file.filename === openapiTypeScriptFilename);
150+
assert(typesFile);
151+
152+
// Verify enums are generated
153+
expect(typesFile.code).toContain("export enum Status");
154+
expect(typesFile.code).toContain("export enum Priority");
155+
156+
// Verify NO duplicate type aliases for enums
157+
const statusTypeAliasRegex = /export type Status = components\["schemas"\]\["Status"\];/;
158+
const priorityTypeAliasRegex = /export type Priority = components\["schemas"\]\["Priority"\];/;
159+
expect(typesFile.code).not.toMatch(statusTypeAliasRegex);
160+
expect(typesFile.code).not.toMatch(priorityTypeAliasRegex);
161+
162+
// Verify non-enum types still get type aliases
163+
expect(typesFile.code).toContain('export type Task = components["schemas"]["Task"];');
164+
165+
expect(typesFile.code).toMatchSnapshot();
166+
},
167+
timeout
168+
);
127169
});

0 commit comments

Comments
 (0)