Skip to content

Commit 5bc3a59

Browse files
committed
Fix wild key issue
1 parent ef58cfc commit 5bc3a59

File tree

4 files changed

+66
-0
lines changed

4 files changed

+66
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"packages/generator/package.json":"Patch"},"note":"Fix wild key issue","date":"2026-04-17T09:02:19.475575400Z"}

packages/generator/src/__tests__/generate-interface.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,3 +2532,55 @@ test('generateInterface preserves JSON endpoints alongside form/multipart endpoi
25322532
// Raw multipart should use FormData | Record<string, unknown>
25332533
expect(result).toContain('FormData | Record<string, unknown>')
25342534
})
2535+
2536+
test('generateInterface emits index signature for additionalProperties (not quoted key)', () => {
2537+
const schema = {
2538+
paths: {
2539+
'/news-categories': {
2540+
post: {
2541+
operationId: 'createNewsCategory',
2542+
requestBody: {
2543+
content: {
2544+
'application/json': {
2545+
schema: {
2546+
$ref: '#/components/schemas/NewsCategoryCreateRequest',
2547+
},
2548+
},
2549+
},
2550+
},
2551+
responses: {
2552+
'201': {
2553+
description: 'Created',
2554+
content: {
2555+
'application/json': {
2556+
schema: { type: 'string' as const },
2557+
},
2558+
},
2559+
},
2560+
},
2561+
},
2562+
},
2563+
},
2564+
components: {
2565+
schemas: {
2566+
NewsCategoryCreateRequest: {
2567+
type: 'object' as const,
2568+
properties: {
2569+
title: {
2570+
type: 'object' as const,
2571+
properties: {},
2572+
required: [],
2573+
additionalProperties: { type: 'string' as const },
2574+
},
2575+
},
2576+
required: ['title'],
2577+
},
2578+
},
2579+
},
2580+
}
2581+
const result = generateInterface(createSchemas(createDocument(schema as any)))
2582+
// Must emit a real TS index signature
2583+
expect(result).toContain('[key: string]: string')
2584+
// Must NOT emit a quoted literal key
2585+
expect(result).not.toContain("'[key: string]'")
2586+
})

packages/generator/src/__tests__/wrap-interface-key-guard.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,11 @@ test.each([
6767
] as const)('wrapInterfaceKeyGuard handles optional keys (ending with ?): %s -> %s', (key, expected) => {
6868
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
6969
})
70+
71+
test.each([
72+
['[key: string]', '[key: string]'],
73+
['[key: number]', '[key: number]'],
74+
['[k: string]', '[k: string]'],
75+
] as const)('wrapInterfaceKeyGuard preserves index signature syntax: %s -> %s', (key, expected) => {
76+
expect(wrapInterfaceKeyGuard(key)).toBe(expected)
77+
})

packages/generator/src/wrap-interface-key-guard.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ export function wrapInterfaceKeyGuard(key: string): string {
44
return key
55
}
66

7+
// Preserve TypeScript index signature syntax (e.g., [key: string], [key: number])
8+
if (/^\[.+:\s*.+\]$/.test(key)) {
9+
return key
10+
}
11+
712
// Check if key ends with '?' (optional marker in TypeScript)
813
// If so, process the base key and add '?' back at the end
914
const isOptional = key.endsWith('?')

0 commit comments

Comments
 (0)