@amritk/generate-parsers 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generators/build-schema.d.ts +4 -1
- package/dist/generators/generate-files.d.ts +5 -0
- package/dist/generators/generate-parser-function.d.ts +6 -1
- package/dist/helpers/collect-imports.d.ts +6 -0
- package/dist/index.js +82 -76
- package/package.json +2 -2
- package/src/generators/build-schema.test.ts +17 -17
- package/src/generators/build-schema.ts +7 -1
- package/src/generators/generate-files.test.ts +65 -52
- package/src/generators/generate-files.ts +11 -4
- package/src/generators/generate-parser-function.test.ts +254 -258
- package/src/generators/generate-parser-function.ts +87 -58
- package/src/helpers/collect-imports.test.ts +44 -84
- package/src/helpers/collect-imports.ts +8 -1
|
@@ -34,9 +34,9 @@ describe('build-schema', () => {
|
|
|
34
34
|
expect(filenames).toContain('contact.ts')
|
|
35
35
|
expect(filenames).toContain('schema.ts')
|
|
36
36
|
const schemaFile = result.find((file) => file.filename === 'schema.ts')
|
|
37
|
-
// The generated schema.ts should declare
|
|
38
|
-
expect(schemaFile?.content).toContain('export type
|
|
39
|
-
expect(schemaFile?.content).toContain('
|
|
37
|
+
// The generated schema.ts should declare Schema and parseSchema from the user's $defs/schema
|
|
38
|
+
expect(schemaFile?.content).toContain('export type Schema')
|
|
39
|
+
expect(schemaFile?.content).toContain('parseSchema')
|
|
40
40
|
})
|
|
41
41
|
|
|
42
42
|
it('applies extensions to matching definitions during build', async () => {
|
|
@@ -193,8 +193,8 @@ describe('build-schema', () => {
|
|
|
193
193
|
const result = await buildSchema(schema, 'Document', undefined, true)
|
|
194
194
|
const documentFile = result.find((file) => file.filename === 'document.ts')
|
|
195
195
|
|
|
196
|
-
expect(documentFile?.content).toContain("import type {
|
|
197
|
-
expect(documentFile?.content).not.toContain('
|
|
196
|
+
expect(documentFile?.content).toContain("import type { Contact } from './contact';")
|
|
197
|
+
expect(documentFile?.content).not.toContain('parseContact')
|
|
198
198
|
})
|
|
199
199
|
|
|
200
200
|
it('still generates files for all $ref definitions in types-only mode', async () => {
|
|
@@ -246,8 +246,8 @@ describe('build-schema', () => {
|
|
|
246
246
|
const result = await buildSchema(schema, 'Document', undefined, true)
|
|
247
247
|
const contactFile = result.find((file) => file.filename === 'contact.ts')
|
|
248
248
|
|
|
249
|
-
expect(contactFile?.content).toContain('export type
|
|
250
|
-
expect(contactFile?.content).not.toContain('export const
|
|
249
|
+
expect(contactFile?.content).toContain('export type Contact')
|
|
250
|
+
expect(contactFile?.content).not.toContain('export const parseContact')
|
|
251
251
|
})
|
|
252
252
|
|
|
253
253
|
it('applies extensions correctly in types-only mode', async () => {
|
|
@@ -273,7 +273,7 @@ describe('build-schema', () => {
|
|
|
273
273
|
// Extension property should still appear in the type
|
|
274
274
|
expect(parameterFile?.content).toContain("'x-enabled'")
|
|
275
275
|
// But no parser should be generated
|
|
276
|
-
expect(parameterFile?.content).not.toContain('
|
|
276
|
+
expect(parameterFile?.content).not.toContain('parseParameter')
|
|
277
277
|
})
|
|
278
278
|
|
|
279
279
|
it('warns and skips when a $ref cannot be resolved', async () => {
|
|
@@ -322,7 +322,7 @@ describe('build-schema', () => {
|
|
|
322
322
|
// Own property should be present in the object literal
|
|
323
323
|
expect(documentFile?.content).toContain('name?:')
|
|
324
324
|
// allOf ref generates an intersection type with the referenced type
|
|
325
|
-
expect(documentFile?.content).toContain('
|
|
325
|
+
expect(documentFile?.content).toContain('Taggable')
|
|
326
326
|
// The taggable definition should generate its own file
|
|
327
327
|
const filenames = result.map((f) => f.filename)
|
|
328
328
|
expect(filenames).toContain('taggable.ts')
|
|
@@ -356,11 +356,11 @@ describe('build-schema', () => {
|
|
|
356
356
|
const documentFile = result.find((file) => file.filename === 'document.ts')
|
|
357
357
|
const filenames = result.map((f) => f.filename)
|
|
358
358
|
|
|
359
|
-
//
|
|
360
|
-
expect(documentFile?.content).toContain('
|
|
359
|
+
// Examples IS imported since allOf generates an intersection type
|
|
360
|
+
expect(documentFile?.content).toContain('Examples')
|
|
361
361
|
// The examples definition generates its own file
|
|
362
362
|
expect(filenames).toContain('examples.ts')
|
|
363
|
-
//
|
|
363
|
+
// ExampleItem is also resolved from additionalProperties
|
|
364
364
|
expect(filenames).toContain('example-item.ts')
|
|
365
365
|
})
|
|
366
366
|
|
|
@@ -435,7 +435,7 @@ describe('build-schema', () => {
|
|
|
435
435
|
const filenames = result.map((f) => f.filename)
|
|
436
436
|
|
|
437
437
|
// specification-extensions is now treated like any other allOf ref
|
|
438
|
-
expect(documentFile?.content).toContain('
|
|
438
|
+
expect(documentFile?.content).toContain('SpecificationExtensions')
|
|
439
439
|
// The specification-extensions definition generates its own file
|
|
440
440
|
expect(filenames).toContain('specification-extensions.ts')
|
|
441
441
|
// x-custom property is NOT inlined into document.ts — it's in specification-extensions.ts
|
|
@@ -546,7 +546,7 @@ describe('build-schema', () => {
|
|
|
546
546
|
// Both files are generated independently — they map to different filenames now
|
|
547
547
|
expect(filenames).toContain('parameter.ts')
|
|
548
548
|
expect(filenames).toContain('parameter-or-reference.ts')
|
|
549
|
-
// The parameter.ts should contain the real
|
|
549
|
+
// The parameter.ts should contain the real Parameter type
|
|
550
550
|
const parameterFile = result.find((f) => f.filename === 'parameter.ts')
|
|
551
551
|
expect(parameterFile?.content).toContain('name')
|
|
552
552
|
expect(parameterFile?.content).toContain('in')
|
|
@@ -572,7 +572,7 @@ describe('build-schema', () => {
|
|
|
572
572
|
expect(indexFile).toBeDefined()
|
|
573
573
|
// Named type + parser + shape-validator exports for each file
|
|
574
574
|
expect(indexFile?.content).toContain(
|
|
575
|
-
"export { type
|
|
575
|
+
"export { type Contact, validateContactShape, parseContact } from './contact';",
|
|
576
576
|
)
|
|
577
577
|
expect(indexFile?.content).toContain(
|
|
578
578
|
"export { type Document, validateDocumentShape, parseDocument } from './document';",
|
|
@@ -601,9 +601,9 @@ describe('build-schema', () => {
|
|
|
601
601
|
|
|
602
602
|
expect(indexFile).toBeDefined()
|
|
603
603
|
// Types-only: export type { ... } syntax, no parsers
|
|
604
|
-
expect(indexFile?.content).toContain("export type {
|
|
604
|
+
expect(indexFile?.content).toContain("export type { Contact } from './contact';")
|
|
605
605
|
expect(indexFile?.content).toContain("export type { Document } from './document';")
|
|
606
|
-
expect(indexFile?.content).not.toContain('
|
|
606
|
+
expect(indexFile?.content).not.toContain('parseContact')
|
|
607
607
|
expect(indexFile?.content).not.toContain('parseDocument')
|
|
608
608
|
// No wildcard exports
|
|
609
609
|
expect(indexFile?.content).not.toContain('export *')
|
|
@@ -96,6 +96,9 @@ const extractDynamicAnchorDefs = (schema: JSONSchema): string[] => {
|
|
|
96
96
|
* directory while the helper sources are emitted once at the output root.
|
|
97
97
|
* @param readonly - When true, every property, array, and record in the generated type
|
|
98
98
|
* definitions is emitted as `readonly`.
|
|
99
|
+
* @param typeSuffix - Suffix appended to every type/parser name derived from a `$ref`
|
|
100
|
+
* (e.g. `'Object'` → `ContactObject`). Defaults to `''` (no suffix). The root type name
|
|
101
|
+
* is used verbatim and is not affected by this suffix.
|
|
99
102
|
* @returns An array of generated TypeScript files
|
|
100
103
|
*
|
|
101
104
|
* @example
|
|
@@ -138,6 +141,7 @@ export const buildSchema = async (
|
|
|
138
141
|
helpersMode: HelpersMode = 'package',
|
|
139
142
|
helpersImportPrefix = './',
|
|
140
143
|
readonly = false,
|
|
144
|
+
typeSuffix = '',
|
|
141
145
|
): Promise<GeneratedFile[]> => {
|
|
142
146
|
// Upgrade draft-07 schemas to 2020-12 conventions before processing.
|
|
143
147
|
// This renames `definitions` → `$defs` recursively so the rest of the
|
|
@@ -165,6 +169,7 @@ export const buildSchema = async (
|
|
|
165
169
|
helpersMode,
|
|
166
170
|
helpersImportPrefix,
|
|
167
171
|
readonly,
|
|
172
|
+
typeSuffix,
|
|
168
173
|
...(logWarnings !== undefined ? { logWarnings } : {}),
|
|
169
174
|
...(strict !== undefined ? { strict } : {}),
|
|
170
175
|
})
|
|
@@ -206,7 +211,7 @@ export const buildSchema = async (
|
|
|
206
211
|
|
|
207
212
|
// Generate file for this ref, resolving any $dynamicRef to $ref first
|
|
208
213
|
// and applying any matching extensions for this definition
|
|
209
|
-
const typeName = refToName(ref)
|
|
214
|
+
const typeName = refToName(ref, typeSuffix)
|
|
210
215
|
const filename = refToFilename(ref)
|
|
211
216
|
const processedSchema = resolveDynamicRefs(resolvedSchema as JSONSchema, dynamicRefMap)
|
|
212
217
|
const extendedSchema = extensions ? applySchemaExtensions(processedSchema, filename, extensions) : processedSchema
|
|
@@ -217,6 +222,7 @@ export const buildSchema = async (
|
|
|
217
222
|
helpersMode,
|
|
218
223
|
helpersImportPrefix,
|
|
219
224
|
readonly,
|
|
225
|
+
typeSuffix,
|
|
220
226
|
...(logWarnings !== undefined ? { logWarnings } : {}),
|
|
221
227
|
...(strict !== undefined ? { strict } : {}),
|
|
222
228
|
})
|
|
@@ -13,9 +13,7 @@ describe('generate-files', () => {
|
|
|
13
13
|
|
|
14
14
|
const { content: result } = generateFile(schema, 'Document')
|
|
15
15
|
|
|
16
|
-
expect(result).toContain(
|
|
17
|
-
"import { type ContactObject, parseContactObject, validateContactObjectShape } from './contact';",
|
|
18
|
-
)
|
|
16
|
+
expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
|
|
19
17
|
})
|
|
20
18
|
|
|
21
19
|
it('calls imported parser for optional $ref property', () => {
|
|
@@ -29,7 +27,7 @@ describe('generate-files', () => {
|
|
|
29
27
|
const { content: result } = generateFile(schema, 'Document')
|
|
30
28
|
|
|
31
29
|
// With local variable caching, optional $ref properties use the cached variable
|
|
32
|
-
expect(result).toContain('_contact !== undefined && { contact:
|
|
30
|
+
expect(result).toContain('_contact !== undefined && { contact: parseContact(_contact) }')
|
|
33
31
|
})
|
|
34
32
|
|
|
35
33
|
it('calls imported parser for required $ref property', () => {
|
|
@@ -44,7 +42,7 @@ describe('generate-files', () => {
|
|
|
44
42
|
const { content: result } = generateFile(schema, 'Document')
|
|
45
43
|
|
|
46
44
|
// With local variable caching, required $ref properties use the cached variable
|
|
47
|
-
expect(result).toContain('contact:
|
|
45
|
+
expect(result).toContain('contact: parseContact(_contact),')
|
|
48
46
|
})
|
|
49
47
|
|
|
50
48
|
it('generates combined imports for multiple $ref properties', () => {
|
|
@@ -58,12 +56,8 @@ describe('generate-files', () => {
|
|
|
58
56
|
|
|
59
57
|
const { content: result } = generateFile(schema, 'Document')
|
|
60
58
|
|
|
61
|
-
expect(result).toContain(
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
expect(result).toContain(
|
|
65
|
-
"import { type ServerObject, parseServerObject, validateServerObjectShape } from './server';",
|
|
66
|
-
)
|
|
59
|
+
expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
|
|
60
|
+
expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
|
|
67
61
|
})
|
|
68
62
|
|
|
69
63
|
it('generates correct parser name for multi-word $ref', () => {
|
|
@@ -77,9 +71,9 @@ describe('generate-files', () => {
|
|
|
77
71
|
const { content: result } = generateFile(schema, 'Document')
|
|
78
72
|
|
|
79
73
|
expect(result).toContain(
|
|
80
|
-
"import { type
|
|
74
|
+
"import { type ExternalDocumentation, parseExternalDocumentation, validateExternalDocumentationShape } from './external-documentation';",
|
|
81
75
|
)
|
|
82
|
-
expect(result).toContain('
|
|
76
|
+
expect(result).toContain('parseExternalDocumentation(_externalDoc)')
|
|
83
77
|
})
|
|
84
78
|
|
|
85
79
|
it('does not generate ref imports when no $ref properties exist', () => {
|
|
@@ -113,7 +107,7 @@ describe('generate-files', () => {
|
|
|
113
107
|
// Non-ref property should have inline validation against the cached local
|
|
114
108
|
expect(result).toContain('typeof _name === "string"')
|
|
115
109
|
// Ref property should call imported parser using cached variable
|
|
116
|
-
expect(result).toContain('
|
|
110
|
+
expect(result).toContain('parseContact(_contact)')
|
|
117
111
|
})
|
|
118
112
|
|
|
119
113
|
it('generates a correct import for -or-reference style $ref names', () => {
|
|
@@ -128,9 +122,9 @@ describe('generate-files', () => {
|
|
|
128
122
|
const { content: result } = generateFile(schema, 'Document')
|
|
129
123
|
|
|
130
124
|
expect(result).toContain(
|
|
131
|
-
"import { type
|
|
125
|
+
"import { type CallbacksOrReference, parseCallbacksOrReference, validateCallbacksOrReferenceShape } from './callbacks-or-reference';",
|
|
132
126
|
)
|
|
133
|
-
expect(result).toContain('
|
|
127
|
+
expect(result).toContain('parseCallbacksOrReference(_callbacks)')
|
|
134
128
|
})
|
|
135
129
|
|
|
136
130
|
it('generates arrow function parser', () => {
|
|
@@ -160,11 +154,9 @@ describe('generate-files', () => {
|
|
|
160
154
|
|
|
161
155
|
const { content: result } = generateFile(schema, 'Document')
|
|
162
156
|
|
|
163
|
-
expect(result).toContain(
|
|
164
|
-
"import { type ServerObject, parseServerObject, validateServerObjectShape } from './server';",
|
|
165
|
-
)
|
|
157
|
+
expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
|
|
166
158
|
// With local variable caching, array validators use the cached variable
|
|
167
|
-
expect(result).toContain('validateArray(_servers,
|
|
159
|
+
expect(result).toContain('validateArray(_servers, parseServer)')
|
|
168
160
|
})
|
|
169
161
|
|
|
170
162
|
it('maps array items through imported parser for required array with $ref items', () => {
|
|
@@ -182,14 +174,12 @@ describe('generate-files', () => {
|
|
|
182
174
|
const { content: result } = generateFile(schema, 'Document')
|
|
183
175
|
|
|
184
176
|
// Check for required imports
|
|
185
|
-
expect(result).toContain(
|
|
186
|
-
"import { type ServerObject, parseServerObject, validateServerObjectShape } from './server';",
|
|
187
|
-
)
|
|
177
|
+
expect(result).toContain("import { type Server, parseServer, validateServerShape } from './server';")
|
|
188
178
|
expect(result).toContain("import { validateArray } from '@amritk/helpers/validate-array';")
|
|
189
179
|
expect(result).toContain("import { isObject } from '@amritk/helpers/is-object';")
|
|
190
180
|
|
|
191
181
|
// Check for required array validation using cached variable
|
|
192
|
-
expect(result).toContain('servers: validateArray(_servers,
|
|
182
|
+
expect(result).toContain('servers: validateArray(_servers, parseServer),')
|
|
193
183
|
|
|
194
184
|
// Should not use spread operator for required fields
|
|
195
185
|
expect(result).not.toContain('...(_servers')
|
|
@@ -261,8 +251,8 @@ describe('generate-files', () => {
|
|
|
261
251
|
|
|
262
252
|
const { content: result } = generateFile(schema, 'Document', { typesOnly: true })
|
|
263
253
|
|
|
264
|
-
expect(result).toContain("import type {
|
|
265
|
-
expect(result).not.toContain('
|
|
254
|
+
expect(result).toContain("import type { Contact } from './contact';")
|
|
255
|
+
expect(result).not.toContain('parseContact')
|
|
266
256
|
})
|
|
267
257
|
|
|
268
258
|
it('does not include runtime helper imports in types-only mode', () => {
|
|
@@ -311,8 +301,8 @@ describe('generate-files', () => {
|
|
|
311
301
|
|
|
312
302
|
const { content: result } = generateFile(schema, 'Document', { typesOnly: true })
|
|
313
303
|
|
|
314
|
-
expect(result).toContain("import type {
|
|
315
|
-
expect(result).not.toContain('
|
|
304
|
+
expect(result).toContain("import type { Server } from './server';")
|
|
305
|
+
expect(result).not.toContain('parseServer')
|
|
316
306
|
expect(result).not.toContain('validateArray')
|
|
317
307
|
})
|
|
318
308
|
|
|
@@ -324,8 +314,8 @@ describe('generate-files', () => {
|
|
|
324
314
|
|
|
325
315
|
const { content: result } = generateFile(schema, 'Paths', { typesOnly: true })
|
|
326
316
|
|
|
327
|
-
expect(result).toContain("import type {
|
|
328
|
-
expect(result).not.toContain('
|
|
317
|
+
expect(result).toContain("import type { PathItem } from './path-item';")
|
|
318
|
+
expect(result).not.toContain('parsePathItem')
|
|
329
319
|
expect(result).not.toContain('validateRecord')
|
|
330
320
|
})
|
|
331
321
|
|
|
@@ -336,10 +326,10 @@ describe('generate-files', () => {
|
|
|
336
326
|
|
|
337
327
|
const { content: result } = generateFile(schema, 'Pet', { typesOnly: true })
|
|
338
328
|
|
|
339
|
-
expect(result).toContain("import type {
|
|
340
|
-
expect(result).toContain("import type {
|
|
341
|
-
expect(result).not.toContain('
|
|
342
|
-
expect(result).not.toContain('
|
|
329
|
+
expect(result).toContain("import type { Cat } from './cat';")
|
|
330
|
+
expect(result).toContain("import type { Dog } from './dog';")
|
|
331
|
+
expect(result).not.toContain('parseCat')
|
|
332
|
+
expect(result).not.toContain('parseDog')
|
|
343
333
|
})
|
|
344
334
|
|
|
345
335
|
it('generates type-only imports for anyOf $ref variants in types-only mode', () => {
|
|
@@ -354,10 +344,10 @@ describe('generate-files', () => {
|
|
|
354
344
|
|
|
355
345
|
const { content: result } = generateFile(schema, 'Wrapper', { typesOnly: true })
|
|
356
346
|
|
|
357
|
-
expect(result).toContain("import type {
|
|
358
|
-
expect(result).toContain("import type {
|
|
359
|
-
expect(result).not.toContain('
|
|
360
|
-
expect(result).not.toContain('
|
|
347
|
+
expect(result).toContain("import type { StringValue } from './string-value';")
|
|
348
|
+
expect(result).toContain("import type { NumberValue } from './number-value';")
|
|
349
|
+
expect(result).not.toContain('parseStringValue')
|
|
350
|
+
expect(result).not.toContain('parseNumberValue')
|
|
361
351
|
})
|
|
362
352
|
|
|
363
353
|
it('does not change default behavior when typesOnly is false', () => {
|
|
@@ -372,16 +362,14 @@ describe('generate-files', () => {
|
|
|
372
362
|
const { content: explicitFalseResult } = generateFile(schema, 'Document', { typesOnly: false })
|
|
373
363
|
|
|
374
364
|
expect(defaultResult).toBe(explicitFalseResult)
|
|
375
|
-
expect(defaultResult).toContain(
|
|
376
|
-
"import { type ContactObject, parseContactObject, validateContactObjectShape } from './contact';",
|
|
377
|
-
)
|
|
365
|
+
expect(defaultResult).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
|
|
378
366
|
expect(defaultResult).toContain('export const parseDocument')
|
|
379
367
|
})
|
|
380
368
|
|
|
381
369
|
it('does not generate a self-import for recursive schemas in types-only mode', () => {
|
|
382
|
-
// Mirrors the 3.2.0 encoding schema:
|
|
370
|
+
// Mirrors the 3.2.0 encoding schema: Encoding has properties itemEncoding, prefixEncoding,
|
|
383
371
|
// and encoding that all $ref back to #/$defs/encoding. The generated encoding.ts must not
|
|
384
|
-
// import
|
|
372
|
+
// import Encoding from itself.
|
|
385
373
|
const schema = {
|
|
386
374
|
type: 'object',
|
|
387
375
|
properties: {
|
|
@@ -401,14 +389,14 @@ describe('generate-files', () => {
|
|
|
401
389
|
},
|
|
402
390
|
}
|
|
403
391
|
|
|
404
|
-
const { content: result } = generateFile(schema, '
|
|
392
|
+
const { content: result } = generateFile(schema, 'Encoding', {
|
|
405
393
|
typesOnly: true,
|
|
406
394
|
selfRef: '#/$defs/encoding',
|
|
407
395
|
})
|
|
408
396
|
|
|
409
|
-
expect(result).not.toContain("import type {
|
|
410
|
-
expect(result).toContain("import type {
|
|
411
|
-
expect(result).toContain('export type
|
|
397
|
+
expect(result).not.toContain("import type { Encoding } from './encoding';")
|
|
398
|
+
expect(result).toContain("import type { HeaderOrReference } from './header-or-reference';")
|
|
399
|
+
expect(result).toContain('export type Encoding')
|
|
412
400
|
})
|
|
413
401
|
|
|
414
402
|
it('does not generate a self-import for recursive schemas in full parser mode', () => {
|
|
@@ -423,13 +411,38 @@ describe('generate-files', () => {
|
|
|
423
411
|
},
|
|
424
412
|
}
|
|
425
413
|
|
|
426
|
-
const { content: result } = generateFile(schema, '
|
|
414
|
+
const { content: result } = generateFile(schema, 'Encoding', { selfRef: '#/$defs/encoding' })
|
|
427
415
|
|
|
428
|
-
expect(result).not.toContain(
|
|
429
|
-
"import { type EncodingObject, parseEncodingObject, validateEncodingObjectShape } from './encoding';",
|
|
430
|
-
)
|
|
416
|
+
expect(result).not.toContain("import { type Encoding, parseEncoding, validateEncodingShape } from './encoding';")
|
|
431
417
|
expect(result).toContain(
|
|
432
|
-
"import { type
|
|
418
|
+
"import { type HeaderOrReference, parseHeaderOrReference, validateHeaderOrReferenceShape } from './header-or-reference';",
|
|
433
419
|
)
|
|
434
420
|
})
|
|
421
|
+
|
|
422
|
+
describe('typeSuffix', () => {
|
|
423
|
+
const schema = {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
contact: { $ref: '#/$defs/contact' },
|
|
427
|
+
},
|
|
428
|
+
required: ['contact'],
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
it('appends the suffix to ref-derived type, parser, and import names', () => {
|
|
432
|
+
const { content: result } = generateFile(schema, 'Document', { typeSuffix: 'Object' })
|
|
433
|
+
|
|
434
|
+
expect(result).toContain(
|
|
435
|
+
"import { type ContactObject, parseContactObject, validateContactObjectShape } from './contact';",
|
|
436
|
+
)
|
|
437
|
+
expect(result).toContain('contact: ContactObject;')
|
|
438
|
+
expect(result).toContain('contact: parseContactObject(_contact)')
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('emits no suffix by default', () => {
|
|
442
|
+
const { content: result } = generateFile(schema, 'Document')
|
|
443
|
+
|
|
444
|
+
expect(result).toContain("import { type Contact, parseContact, validateContactShape } from './contact';")
|
|
445
|
+
expect(result).not.toContain('ContactObject')
|
|
446
|
+
})
|
|
447
|
+
})
|
|
435
448
|
})
|
|
@@ -59,6 +59,11 @@ type GenerateFileOptions = {
|
|
|
59
59
|
* definitions is emitted as `readonly`.
|
|
60
60
|
*/
|
|
61
61
|
readonly readonly?: boolean
|
|
62
|
+
/**
|
|
63
|
+
* Suffix appended to every type/parser name derived from a `$ref`.
|
|
64
|
+
* Defaults to `''` (no suffix). Set to e.g. `'Object'` to emit `ContactObject`.
|
|
65
|
+
*/
|
|
66
|
+
readonly typeSuffix?: string
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
/** Result of generating a single parser file. */
|
|
@@ -105,11 +110,12 @@ export const generateFile = (
|
|
|
105
110
|
const selfRef = options?.selfRef
|
|
106
111
|
const rootSchema = options?.rootSchema
|
|
107
112
|
const helpersMode: HelpersMode = options?.helpersMode ?? 'package'
|
|
108
|
-
const
|
|
113
|
+
const typeSuffix = options?.typeSuffix ?? ''
|
|
114
|
+
const typeDefinition = generateTypeDefinition(schema, typeName, { readonly: options?.readonly ?? false, typeSuffix })
|
|
109
115
|
|
|
110
116
|
if (typesOnly) {
|
|
111
117
|
// In types-only mode, skip the parser function and use type-only imports
|
|
112
|
-
const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema })
|
|
118
|
+
const imports = collectImports(schema, { typesOnly: true, selfRef, rootSchema, typeSuffix })
|
|
113
119
|
let result = ''
|
|
114
120
|
|
|
115
121
|
if (imports.length > 0) {
|
|
@@ -125,13 +131,14 @@ export const generateFile = (
|
|
|
125
131
|
|
|
126
132
|
const parserFunction = generateParserFunction(schema, typeName, {
|
|
127
133
|
useRefImports: true,
|
|
134
|
+
typeSuffix,
|
|
128
135
|
...(options?.logWarnings !== undefined ? { logWarnings: options.logWarnings } : {}),
|
|
129
136
|
...(options?.strict !== undefined ? { strict: options.strict } : {}),
|
|
130
137
|
})
|
|
131
|
-
const shapeValidator = generateShapeValidator(schema, typeName, true)
|
|
138
|
+
const shapeValidator = generateShapeValidator(schema, typeName, true, typeSuffix)
|
|
132
139
|
const combinedFunctions = `${shapeValidator}\n\n${parserFunction}`
|
|
133
140
|
const helpers: CollectedHelpers = collectHelpers(combinedFunctions, helpersMode, options?.helpersImportPrefix)
|
|
134
|
-
const imports = [...collectImports(schema, { selfRef, rootSchema }), ...helpers.imports]
|
|
141
|
+
const imports = [...collectImports(schema, { selfRef, rootSchema, typeSuffix }), ...helpers.imports]
|
|
135
142
|
|
|
136
143
|
// Build file output using string concatenation instead of array join for performance
|
|
137
144
|
let result = ''
|