@kubb/ast 5.0.0-beta.6 → 5.0.0-beta.61

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.
Files changed (102) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +51 -27
  3. package/dist/chunk-CNktS9qV.js +17 -0
  4. package/dist/defineMacro-BLIR6k-j.d.ts +475 -0
  5. package/dist/defineMacro-BTXvS8nI.js +106 -0
  6. package/dist/defineMacro-BTXvS8nI.js.map +1 -0
  7. package/dist/defineMacro-Bv9R_9a2.cjs +123 -0
  8. package/dist/defineMacro-Bv9R_9a2.cjs.map +1 -0
  9. package/dist/extractStringsFromNodes-Cja-xxx5.js +29 -0
  10. package/dist/extractStringsFromNodes-Cja-xxx5.js.map +1 -0
  11. package/dist/extractStringsFromNodes-DKgDjFO0.cjs +34 -0
  12. package/dist/extractStringsFromNodes-DKgDjFO0.cjs.map +1 -0
  13. package/dist/extractStringsFromNodes-p4mX1TQD.d.ts +14 -0
  14. package/dist/factory-CZNOGI-N.js +283 -0
  15. package/dist/factory-CZNOGI-N.js.map +1 -0
  16. package/dist/factory-DG1CVkEb.cjs +300 -0
  17. package/dist/factory-DG1CVkEb.cjs.map +1 -0
  18. package/dist/factory.cjs +29 -0
  19. package/dist/factory.d.ts +62 -0
  20. package/dist/factory.js +3 -0
  21. package/dist/index-BzjwdK2M.d.ts +2433 -0
  22. package/dist/index.cjs +444 -2180
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.ts +94 -3408
  25. package/dist/index.js +395 -2101
  26. package/dist/index.js.map +1 -1
  27. package/dist/macros.cjs +117 -0
  28. package/dist/macros.cjs.map +1 -0
  29. package/dist/macros.d.ts +59 -0
  30. package/dist/macros.js +115 -0
  31. package/dist/macros.js.map +1 -0
  32. package/dist/operationParams-BZ07xDm0.d.ts +204 -0
  33. package/dist/response-KUdWiDWw.js +658 -0
  34. package/dist/response-KUdWiDWw.js.map +1 -0
  35. package/dist/response-hnSw2NKE.cjs +1027 -0
  36. package/dist/response-hnSw2NKE.cjs.map +1 -0
  37. package/dist/types-DyDzizSf.d.ts +364 -0
  38. package/dist/types.cjs +0 -0
  39. package/dist/types.d.ts +6 -0
  40. package/dist/types.js +1 -0
  41. package/dist/utils-BLJwyza-.cjs +912 -0
  42. package/dist/utils-BLJwyza-.cjs.map +1 -0
  43. package/dist/utils-CF_-Pn_c.js +770 -0
  44. package/dist/utils-CF_-Pn_c.js.map +1 -0
  45. package/dist/utils.cjs +36 -0
  46. package/dist/utils.d.ts +358 -0
  47. package/dist/utils.js +4 -0
  48. package/dist/visitor-DJ6ZEJvq.js +548 -0
  49. package/dist/visitor-DJ6ZEJvq.js.map +1 -0
  50. package/dist/visitor-DpKZ9Tk0.cjs +654 -0
  51. package/dist/visitor-DpKZ9Tk0.cjs.map +1 -0
  52. package/package.json +21 -6
  53. package/src/constants.ts +19 -64
  54. package/src/dedupe.ts +239 -0
  55. package/src/defineMacro.ts +132 -0
  56. package/src/dialect.ts +53 -0
  57. package/src/factory.ts +67 -678
  58. package/src/guards.ts +10 -92
  59. package/src/index.ts +13 -44
  60. package/src/infer.ts +16 -14
  61. package/src/macros/index.ts +3 -0
  62. package/src/macros/macroDiscriminatorEnum.ts +44 -0
  63. package/src/macros/macroEnumName.ts +25 -0
  64. package/src/macros/macroSimplifyUnion.ts +50 -0
  65. package/src/mocks.ts +7 -127
  66. package/src/node.ts +128 -0
  67. package/src/nodes/base.ts +5 -12
  68. package/src/nodes/code.ts +165 -74
  69. package/src/nodes/content.ts +56 -0
  70. package/src/nodes/file.ts +97 -36
  71. package/src/nodes/function.ts +216 -156
  72. package/src/nodes/http.ts +1 -35
  73. package/src/nodes/index.ts +23 -15
  74. package/src/nodes/input.ts +140 -0
  75. package/src/nodes/operation.ts +122 -68
  76. package/src/nodes/output.ts +23 -0
  77. package/src/nodes/parameter.ts +33 -3
  78. package/src/nodes/property.ts +36 -3
  79. package/src/nodes/requestBody.ts +61 -0
  80. package/src/nodes/response.ts +58 -13
  81. package/src/nodes/schema.ts +93 -17
  82. package/src/printer.ts +48 -42
  83. package/src/registry.ts +75 -0
  84. package/src/signature.ts +207 -0
  85. package/src/types.ts +8 -68
  86. package/src/utils/codegen.ts +104 -0
  87. package/src/utils/extractStringsFromNodes.ts +34 -0
  88. package/src/utils/fileMerge.ts +184 -0
  89. package/src/utils/index.ts +11 -0
  90. package/src/utils/operationParams.ts +353 -0
  91. package/src/utils/refs.ts +138 -0
  92. package/src/utils/schemaGraph.ts +169 -0
  93. package/src/utils/schemaMerge.ts +34 -0
  94. package/src/utils/schemaTraversal.ts +86 -0
  95. package/src/utils/strings.ts +139 -0
  96. package/src/visitor.ts +227 -289
  97. package/dist/chunk--u3MIqq1.js +0 -8
  98. package/src/nodes/root.ts +0 -64
  99. package/src/refs.ts +0 -67
  100. package/src/resolvers.ts +0 -45
  101. package/src/transformers.ts +0 -159
  102. package/src/utils.ts +0 -915
package/src/factory.ts CHANGED
@@ -1,510 +1,56 @@
1
- import { createHash } from 'node:crypto'
1
+ import { hash } from 'node:crypto'
2
2
  import path from 'node:path'
3
3
  import { trimExtName } from '@internals/utils'
4
- import type { InferSchemaNode } from './infer.ts'
5
- import type {
6
- ArrowFunctionNode,
7
- BreakNode,
8
- ConstNode,
9
- ExportNode,
10
- FileNode,
11
- FunctionNode,
12
- FunctionParameterNode,
13
- FunctionParametersNode,
14
- ImportNode,
15
- InputNode,
16
- JsxNode,
17
- ObjectSchemaNode,
18
- OperationNode,
19
- OutputNode,
20
- ParameterGroupNode,
21
- ParameterNode,
22
- ParamsTypeNode,
23
- PrimitiveSchemaType,
24
- PropertyNode,
25
- ResponseNode,
26
- SchemaNode,
27
- SourceNode,
28
- TextNode,
29
- TypeNode,
30
- } from './nodes/index.ts'
31
- import { combineExports, combineImports, combineSources, extractStringsFromNodes } from './utils.ts'
32
-
33
- /**
34
- * Syncs property/parameter schema optionality flags from `required` and `schema.nullable`.
35
- *
36
- * - `optional` is set for non-required, non-nullable schemas.
37
- * - `nullish` is set for non-required, nullable schemas.
38
- */
39
- export function syncOptionality(schema: SchemaNode, required: boolean): SchemaNode {
40
- const nullable = schema.nullable ?? false
41
-
42
- return {
43
- ...schema,
44
- optional: !required && !nullable ? true : undefined,
45
- nullish: !required && nullable ? true : undefined,
46
- }
47
- }
48
-
49
- /**
50
- * Distributive `Omit` that preserves each member of a union.
51
- *
52
- * @example
53
- * ```ts
54
- * type A = { kind: 'a'; keep: string; drop: number }
55
- * type B = { kind: 'b'; keep: boolean; drop: number }
56
- * type Result = DistributiveOmit<A | B, 'drop'>
57
- * // -> { kind: 'a'; keep: string } | { kind: 'b'; keep: boolean }
58
- * ```
59
- */
60
- export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never
61
-
62
- type CreateSchemaObjectInput = Omit<ObjectSchemaNode, 'kind' | 'properties' | 'primitive'> & { properties?: Array<PropertyNode>; primitive?: 'object' }
63
- type CreateSchemaInput = CreateSchemaObjectInput | DistributiveOmit<Exclude<SchemaNode, ObjectSchemaNode>, 'kind'>
64
- type CreateSchemaOutput<T extends CreateSchemaInput> = InferSchemaNode<T> & {
65
- kind: 'Schema'
66
- }
67
-
68
- /**
69
- * Creates an `InputNode` with stable defaults for `schemas` and `operations`.
70
- *
71
- * @example
72
- * ```ts
73
- * const input = createInput()
74
- * // { kind: 'Input', schemas: [], operations: [] }
75
- * ```
76
- *
77
- * @example
78
- * ```ts
79
- * const input = createInput({ schemas: [petSchema] })
80
- * // keeps default operations: []
81
- * ```
82
- */
83
- export function createInput(overrides: Partial<Omit<InputNode, 'kind'>> = {}): InputNode {
84
- return {
85
- schemas: [],
86
- operations: [],
87
- ...overrides,
88
- kind: 'Input',
89
- }
90
- }
91
-
92
- /**
93
- * Creates an `OutputNode` with a stable default for `files`.
94
- *
95
- * @example
96
- * ```ts
97
- * const output = createOutput()
98
- * // { kind: 'Output', files: [] }
99
- * ```
100
- *
101
- * @example
102
- * ```ts
103
- * const output = createOutput({ files: [petFile] })
104
- * ```
105
- */
106
- export function createOutput(overrides: Partial<Omit<OutputNode, 'kind'>> = {}): OutputNode {
107
- return {
108
- files: [],
109
- ...overrides,
110
- kind: 'Output',
111
- }
112
- }
113
-
114
- /**
115
- * Creates an `OperationNode` with default empty arrays for `tags`, `parameters`, and `responses`.
116
- *
117
- * @example
118
- * ```ts
119
- * const operation = createOperation({
120
- * operationId: 'getPetById',
121
- * method: 'GET',
122
- * path: '/pet/{petId}',
123
- * })
124
- * // tags, parameters, and responses are []
125
- * ```
126
- *
127
- * @example
128
- * ```ts
129
- * const operation = createOperation({
130
- * operationId: 'findPets',
131
- * method: 'GET',
132
- * path: '/pet/findByStatus',
133
- * tags: ['pet'],
134
- * })
135
- * ```
136
- */
137
- export function createOperation(
138
- props: Pick<OperationNode, 'operationId' | 'method' | 'path'> & Partial<Omit<OperationNode, 'kind' | 'operationId' | 'method' | 'path'>>,
139
- ): OperationNode {
140
- return {
141
- tags: [],
142
- parameters: [],
143
- responses: [],
144
- ...props,
145
- kind: 'Operation',
146
- }
147
- }
148
-
149
- /**
150
- * Maps schema `type` to its underlying `primitive`.
151
- * Primitive types map to themselves; special string formats map to `'string'`.
152
- * Complex types (`ref`, `enum`, `union`, `intersection`, `tuple`, `blob`) are left unset.
153
- */
154
- const TYPE_TO_PRIMITIVE: Partial<Record<SchemaNode['type'], PrimitiveSchemaType>> = {
155
- string: 'string',
156
- number: 'number',
157
- integer: 'integer',
158
- bigint: 'bigint',
159
- boolean: 'boolean',
160
- null: 'null',
161
- any: 'any',
162
- unknown: 'unknown',
163
- void: 'void',
164
- never: 'never',
165
- object: 'object',
166
- array: 'array',
167
- date: 'date',
168
- uuid: 'string',
169
- email: 'string',
170
- url: 'string',
171
- datetime: 'string',
172
- time: 'string',
173
- }
174
-
175
- /**
176
- * Creates a `SchemaNode`, narrowed to the variant of `props.type`.
177
- * For object schemas, `properties` defaults to an empty array.
178
- * `primitive` is automatically inferred from `type` when not explicitly provided.
179
- *
180
- * @example
181
- * ```ts
182
- * const scalar = createSchema({ type: 'string' })
183
- * // { kind: 'Schema', type: 'string', primitive: 'string' }
184
- * ```
185
- *
186
- * @example
187
- * ```ts
188
- * const uuid = createSchema({ type: 'uuid' })
189
- * // { kind: 'Schema', type: 'uuid', primitive: 'string' }
190
- * ```
191
- *
192
- * @example
193
- * ```ts
194
- * const object = createSchema({ type: 'object' })
195
- * // { kind: 'Schema', type: 'object', primitive: 'object', properties: [] }
196
- * ```
197
- *
198
- * @example
199
- * ```ts
200
- * const enumSchema = createSchema({
201
- * type: 'enum',
202
- * primitive: 'string',
203
- * enumValues: ['available', 'pending'],
204
- * })
205
- * ```
206
- */
207
- export function createSchema<T extends CreateSchemaInput>(props: T): CreateSchemaOutput<T>
208
- export function createSchema(props: CreateSchemaInput): SchemaNode
209
- export function createSchema(props: CreateSchemaInput): SchemaNode {
210
- const inferredPrimitive = TYPE_TO_PRIMITIVE[props.type as keyof typeof TYPE_TO_PRIMITIVE]
211
-
212
- if (props['type'] === 'object') {
213
- return {
214
- properties: [],
215
- primitive: 'object',
216
- ...props,
217
- kind: 'Schema',
218
- } as CreateSchemaOutput<typeof props>
219
- }
220
-
221
- return {
222
- primitive: inferredPrimitive,
223
- ...props,
224
- kind: 'Schema',
225
- } as CreateSchemaOutput<typeof props>
226
- }
227
-
228
- type UserPropertyNode = Pick<PropertyNode, 'name' | 'schema'> & Partial<Omit<PropertyNode, 'kind' | 'name' | 'schema'>>
229
-
230
- /**
231
- * Creates a `PropertyNode`.
232
- *
233
- * `required` defaults to `false`.
234
- * `schema.optional` and `schema.nullish` are derived from `required` and `schema.nullable`.
235
- *
236
- * @example
237
- * ```ts
238
- * const property = createProperty({
239
- * name: 'status',
240
- * schema: createSchema({ type: 'string' }),
241
- * })
242
- * // required=false, schema.optional=true
243
- * ```
244
- *
245
- * @example
246
- * ```ts
247
- * const property = createProperty({
248
- * name: 'status',
249
- * required: true,
250
- * schema: createSchema({ type: 'string', nullable: true }),
251
- * })
252
- * // required=true, no optional/nullish
253
- * ```
254
- */
255
- export function createProperty(props: UserPropertyNode): PropertyNode {
256
- const required = props.required ?? false
257
-
258
- return {
259
- ...props,
260
- kind: 'Property',
261
- required,
262
- schema: syncOptionality(props.schema, required),
263
- }
264
- }
265
-
266
- /**
267
- * Creates a `ParameterNode`.
268
- *
269
- * `required` defaults to `false`.
270
- * Nested schema flags are set from `required` and `schema.nullable`.
271
- *
272
- * @example
273
- * ```ts
274
- * const param = createParameter({
275
- * name: 'petId',
276
- * in: 'path',
277
- * required: true,
278
- * schema: createSchema({ type: 'string' }),
279
- * })
280
- * ```
281
- *
282
- * @example
283
- * ```ts
284
- * const param = createParameter({
285
- * name: 'status',
286
- * in: 'query',
287
- * schema: createSchema({ type: 'string', nullable: true }),
288
- * })
289
- * // required=false, schema.nullish=true
290
- * ```
291
- */
292
- export function createParameter(
293
- props: Pick<ParameterNode, 'name' | 'in' | 'schema'> & Partial<Omit<ParameterNode, 'kind' | 'name' | 'in' | 'schema'>>,
294
- ): ParameterNode {
295
- const required = props.required ?? false
296
- return {
297
- ...props,
298
- kind: 'Parameter',
299
- required,
300
- schema: syncOptionality(props.schema, required),
301
- }
302
- }
303
-
304
- /**
305
- * Creates a `ResponseNode`.
306
- *
307
- * @example
308
- * ```ts
309
- * const response = createResponse({
310
- * statusCode: '200',
311
- * description: 'Success',
312
- * schema: createSchema({ type: 'object', properties: [] }),
313
- * })
314
- * ```
315
- */
316
- export function createResponse(
317
- props: Pick<ResponseNode, 'statusCode' | 'schema'> & Partial<Omit<ResponseNode, 'kind' | 'statusCode' | 'schema'>>,
318
- ): ResponseNode {
319
- return {
320
- ...props,
321
- kind: 'Response',
322
- }
323
- }
324
-
325
- /**
326
- * Creates a `FunctionParameterNode`.
327
- *
328
- * `optional` defaults to `false`.
329
- *
330
- * @example Required typed param
331
- * ```ts
332
- * createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }) })
333
- * // → petId: string
334
- * ```
335
- *
336
- * @example Optional param
337
- * ```ts
338
- * createFunctionParameter({ name: 'params', type: createParamsType({ variant: 'reference', name: 'QueryParams' }), optional: true })
339
- * // → params?: QueryParams
340
- * ```
341
- *
342
- * @example Param with default (implicitly optional; cannot combine with `optional: true`)
343
- * ```ts
344
- * createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), default: '{}' })
345
- * // → config: RequestConfig = {}
346
- * ```
347
- */
348
- export function createFunctionParameter(
349
- props: { name: string; type?: ParamsTypeNode; rest?: boolean } & ({ optional: true; default?: never } | { optional?: false; default?: string }),
350
- ): FunctionParameterNode {
351
- return {
352
- optional: false,
353
- ...props,
354
- kind: 'FunctionParameter',
355
- } as FunctionParameterNode
356
- }
357
-
358
- /**
359
- * Creates a {@link TypeNode} representing a language-agnostic structured type expression.
360
- *
361
- * Use `variant: 'struct'` for inline anonymous types and `variant: 'member'` for a single
362
- * named field accessed from a group type. Each language's printer renders the variant
363
- * into its own syntax (TypeScript, Python, C#, Kotlin, …).
364
- *
365
- * @example Reference type (TypeScript: `QueryParams`)
366
- * ```ts
367
- * createParamsType({ variant: 'reference', name: 'QueryParams' })
368
- * ```
369
- *
370
- * @example Struct type (TypeScript: `{ petId: string }`)
371
- * ```ts
372
- * createParamsType({ variant: 'struct', properties: [{ name: 'petId', optional: false, type: createParamsType({ variant: 'reference', name: 'string' }) }] })
373
- * ```
374
- *
375
- * @example Member type (TypeScript: `DeletePetPathParams['petId']`)
376
- * ```ts
377
- * createParamsType({ variant: 'member', base: 'DeletePetPathParams', key: 'petId' })
378
- * ```
379
- */
380
- export function createParamsType(
381
- props:
382
- | { variant: 'reference'; name: string }
383
- | {
384
- variant: 'struct'
385
- properties: Array<{
386
- name: string
387
- optional: boolean
388
- type: ParamsTypeNode
389
- }>
390
- }
391
- | { variant: 'member'; base: string; key: string },
392
- ): ParamsTypeNode {
393
- return { ...props, kind: 'ParamsType' } as ParamsTypeNode
394
- }
395
-
396
- /**
397
- * Creates a `ParameterGroupNode` representing a group of related parameters treated as a unit.
398
- *
399
- * @example Grouped param (TypeScript declaration)
400
- * ```ts
401
- * createParameterGroup({
402
- * properties: [
403
- * createFunctionParameter({ name: 'id', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
404
- * createFunctionParameter({ name: 'name', type: createParamsType({ variant: 'reference', name: 'string' }), optional: true }),
405
- * ],
406
- * default: '{}',
407
- * })
408
- * // declaration → { id, name? }: { id: string; name?: string } = {}
409
- * // call → { id, name }
410
- * ```
411
- *
412
- * @example Inline (spread) — children emitted as individual top-level parameters
413
- * ```ts
414
- * createParameterGroup({
415
- * properties: [createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false })],
416
- * inline: true,
417
- * })
418
- * // declaration → petId: string
419
- * // call → petId
420
- * ```
421
- */
422
- export function createParameterGroup(
423
- props: Pick<ParameterGroupNode, 'properties'> & Partial<Omit<ParameterGroupNode, 'kind' | 'properties'>>,
424
- ): ParameterGroupNode {
425
- return {
426
- ...props,
427
- kind: 'ParameterGroup',
428
- }
429
- }
430
-
431
- /**
432
- * Creates a `FunctionParametersNode` from an ordered list of parameters.
433
- *
434
- * @example
435
- * ```ts
436
- * createFunctionParameters({
437
- * params: [
438
- * createFunctionParameter({ name: 'petId', type: createParamsType({ variant: 'reference', name: 'string' }), optional: false }),
439
- * createFunctionParameter({ name: 'config', type: createParamsType({ variant: 'reference', name: 'RequestConfig' }), optional: false, default: '{}' }),
440
- * ],
441
- * })
442
- * ```
443
- *
444
- * @example
445
- * ```ts
446
- * const empty = createFunctionParameters()
447
- * // { kind: 'FunctionParameters', params: [] }
448
- * ```
449
- */
450
- export function createFunctionParameters(props: Partial<Omit<FunctionParametersNode, 'kind'>> = {}): FunctionParametersNode {
451
- return {
452
- params: [],
453
- ...props,
454
- kind: 'FunctionParameters',
4
+ import type { FileNode, Node } from './nodes/index.ts'
5
+ import { extractStringsFromNodes } from './utils/extractStringsFromNodes.ts'
6
+ import { combineExports, combineImports, combineSources } from './utils/fileMerge.ts'
7
+
8
+ // Node constructors, grouped under the `factory` namespace the way the TypeScript compiler exposes
9
+ // `ts.factory.createX`. Aggregating them here lets `export * as factory from './factory.ts'` in the
10
+ // barrel surface every `createX` alongside the `createFile`/`update` helpers from a single module.
11
+ export { createArrowFunction, createBreak, createConst, createFunction, createJsx, createText, createType } from './nodes/code.ts'
12
+ export { createContent } from './nodes/content.ts'
13
+ export { createExport, createImport, createSource } from './nodes/file.ts'
14
+ export { createFunctionParameter, createFunctionParameters, createIndexedAccessType, createObjectBindingPattern, createTypeLiteral } from './nodes/function.ts'
15
+ export { createInput } from './nodes/input.ts'
16
+ export { createOperation } from './nodes/operation.ts'
17
+ export { createOutput } from './nodes/output.ts'
18
+ export { createParameter } from './nodes/parameter.ts'
19
+ export { createProperty } from './nodes/property.ts'
20
+ export { createRequestBody } from './nodes/requestBody.ts'
21
+ export { createResponse } from './nodes/response.ts'
22
+ export { createSchema } from './nodes/schema.ts'
23
+
24
+ /**
25
+ * Identity-preserving node update: returns `node` unchanged when every field in
26
+ * `changes` already equals (by reference) the current value, otherwise a new node
27
+ * with the changes applied.
28
+ *
29
+ * Mirrors the TypeScript compiler's `factory.updateX` contract, pair it with the
30
+ * structural sharing in {@link transform} so a no-op rewrite doesn't allocate and
31
+ * downstream passes can detect "nothing changed" by identity. Comparison is
32
+ * shallow: a structurally-equal but newly-allocated array/object counts as a change.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * update(node, { name: node.name }) // -> same `node` reference
37
+ * update(node, { name: 'renamed' }) // -> new node, `name` replaced
38
+ * ```
39
+ */
40
+ export function update<T extends Node>(node: T, changes: Partial<T>): T {
41
+ for (const key in changes) {
42
+ if (changes[key] !== node[key as keyof T]) {
43
+ return { ...node, ...changes }
44
+ }
455
45
  }
456
- }
457
46
 
458
- /**
459
- * Creates an `ImportNode` representing a language-agnostic import/dependency declaration.
460
- *
461
- * @example Named import
462
- * ```ts
463
- * createImport({ name: ['useState'], path: 'react' })
464
- * // import { useState } from 'react'
465
- * ```
466
- *
467
- * @example Type-only import
468
- * ```ts
469
- * createImport({ name: ['FC'], path: 'react', isTypeOnly: true })
470
- * // import type { FC } from 'react'
471
- * ```
472
- */
473
- export function createImport(props: Omit<ImportNode, 'kind'>): ImportNode {
474
- return { ...props, kind: 'Import' }
475
- }
476
-
477
- /**
478
- * Creates an `ExportNode` representing a language-agnostic export/public API declaration.
479
- *
480
- * @example Named export
481
- * ```ts
482
- * createExport({ name: ['Pet'], path: './Pet' })
483
- * // export { Pet } from './Pet'
484
- * ```
485
- *
486
- * @example Wildcard export
487
- * ```ts
488
- * createExport({ path: './utils' })
489
- * // export * from './utils'
490
- * ```
491
- */
492
- export function createExport(props: Omit<ExportNode, 'kind'>): ExportNode {
493
- return { ...props, kind: 'Export' }
47
+ return node
494
48
  }
495
49
 
496
50
  /**
497
- * Creates a `SourceNode` representing a fragment of source code within a file.
498
- *
499
- * @example
500
- * ```ts
501
- * createSource({ name: 'Pet', nodes: [createText('export type Pet = { id: number }')], isExportable: true })
502
- * ```
51
+ * Input descriptor for {@link createFile}, before `id`, `name`, and `extname` are computed
52
+ * and `imports`/`exports`/`sources` are deduplicated.
503
53
  */
504
- export function createSource(props: Omit<SourceNode, 'kind'>): SourceNode {
505
- return { ...props, kind: 'Source' }
506
- }
507
-
508
54
  export type UserFileNode<TMeta extends object = object> = Omit<FileNode<TMeta>, 'kind' | 'id' | 'name' | 'extname' | 'imports' | 'exports' | 'sources'> &
509
55
  Pick<Partial<FileNode<TMeta>>, 'imports' | 'exports' | 'sources'>
510
56
 
@@ -512,9 +58,9 @@ export type UserFileNode<TMeta extends object = object> = Omit<FileNode<TMeta>,
512
58
  * Creates a fully resolved `FileNode` from a file input descriptor.
513
59
  *
514
60
  * Computes:
515
- * - `id` SHA256 hash of the file path
516
- * - `name` `baseName` without extension
517
- * - `extname` extension extracted from `baseName`
61
+ * - `id` SHA256 hash of the file path
62
+ * - `name` `baseName` without extension
63
+ * - `extname` extension extracted from `baseName`
518
64
  *
519
65
  * Deduplicates:
520
66
  * - `sources` via `combineSources`
@@ -551,13 +97,30 @@ export function createFile<TMeta extends object = object>(input: UserFileNode<TM
551
97
  .filter(Boolean)
552
98
  .join('\n\n')
553
99
  const resolvedExports = input.exports?.length ? combineExports(input.exports) : []
554
- const resolvedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || undefined) : []
100
+ const combinedImports = input.imports?.length ? combineImports(input.imports, resolvedExports, source || undefined) : []
101
+ const localNames = new Set((input.sources ?? []).map((item) => item.name).filter((name): name is string => Boolean(name)))
102
+ const nameOf = (item: string | { propertyName: string; name?: string }): string => (typeof item === 'string' ? item : (item.name ?? item.propertyName))
103
+ // Drop self-imports. Consolidating output (`mode: 'file'`) can place a symbol's
104
+ // definition and a cross-file import of it in the same file. The first pass catches imports that
105
+ // resolve to this file's own path. The second drops imports of names the file already defines,
106
+ // the case consolidation produces when the import path no longer matches `input.path`. Sources
107
+ // stay intact, so the local definition remains. Bare specifiers like `'zod'` never match a path.
108
+ const resolvedImports = combinedImports
109
+ .filter((imp) => imp.path !== input.path)
110
+ .flatMap((imp) => {
111
+ if (!Array.isArray(imp.name)) {
112
+ return typeof imp.name === 'string' && localNames.has(imp.name) ? [] : [imp]
113
+ }
114
+ const kept = imp.name.filter((item) => !localNames.has(nameOf(item)))
115
+ if (!kept.length) return []
116
+ return [kept.length === imp.name.length ? imp : { ...imp, name: kept }]
117
+ })
555
118
  const resolvedSources = input.sources?.length ? combineSources(input.sources) : []
556
119
 
557
120
  return {
558
121
  kind: 'File',
559
122
  ...input,
560
- id: createHash('sha256').update(input.path).digest('hex'),
123
+ id: hash('sha256', input.path, 'hex'),
561
124
  name: trimExtName(input.baseName),
562
125
  extname,
563
126
  imports: resolvedImports,
@@ -566,177 +129,3 @@ export function createFile<TMeta extends object = object>(input: UserFileNode<TM
566
129
  meta: input.meta ?? ({} as TMeta),
567
130
  }
568
131
  }
569
-
570
- /**
571
- * Creates a `ConstNode` representing a TypeScript `const` declaration.
572
- *
573
- * Mirrors the `Const` component from `@kubb/renderer-jsx`.
574
- * The component's `children` are represented as `nodes`.
575
- *
576
- * @example Simple constant
577
- * ```ts
578
- * createConst({ name: 'pet' })
579
- * // const pet = ...
580
- * ```
581
- *
582
- * @example Exported constant with type and `as const`
583
- * ```ts
584
- * createConst({ name: 'pets', export: true, type: 'Pet[]', asConst: true })
585
- * // export const pets: Pet[] = ... as const
586
- * ```
587
- *
588
- * @example With JSDoc and child nodes
589
- * ```ts
590
- * createConst({
591
- * name: 'config',
592
- * export: true,
593
- * JSDoc: { comments: ['@description App configuration'] },
594
- * nodes: [],
595
- * })
596
- * ```
597
- */
598
- export function createConst(props: Omit<ConstNode, 'kind'>): ConstNode {
599
- return { ...props, kind: 'Const' }
600
- }
601
-
602
- /**
603
- * Creates a `TypeNode` representing a TypeScript `type` alias declaration.
604
- *
605
- * Mirrors the `Type` component from `@kubb/renderer-jsx`.
606
- * The component's `children` are represented as `nodes`.
607
- *
608
- * @example Simple type alias
609
- * ```ts
610
- * createType({ name: 'Pet' })
611
- * // type Pet = ...
612
- * ```
613
- *
614
- * @example Exported type with JSDoc
615
- * ```ts
616
- * createType({
617
- * name: 'PetStatus',
618
- * export: true,
619
- * JSDoc: { comments: ['@description Status of a pet'] },
620
- * })
621
- * // export type PetStatus = ...
622
- * ```
623
- */
624
- export function createType(props: Omit<TypeNode, 'kind'>): TypeNode {
625
- return { ...props, kind: 'Type' }
626
- }
627
-
628
- /**
629
- * Creates a `FunctionNode` representing a TypeScript `function` declaration.
630
- *
631
- * Mirrors the `Function` component from `@kubb/renderer-jsx`.
632
- * The component's `children` are represented as `nodes`.
633
- *
634
- * @example Simple function
635
- * ```ts
636
- * createFunction({ name: 'getPet' })
637
- * // function getPet() { ... }
638
- * ```
639
- *
640
- * @example Exported async function with return type
641
- * ```ts
642
- * createFunction({ name: 'fetchPet', export: true, async: true, returnType: 'Pet' })
643
- * // export async function fetchPet(): Promise<Pet> { ... }
644
- * ```
645
- *
646
- * @example Function with generics and params
647
- * ```ts
648
- * createFunction({
649
- * name: 'identity',
650
- * export: true,
651
- * generics: ['T'],
652
- * params: 'value: T',
653
- * returnType: 'T',
654
- * })
655
- * // export function identity<T>(value: T): T { ... }
656
- * ```
657
- */
658
- export function createFunction(props: Omit<FunctionNode, 'kind'>): FunctionNode {
659
- return { ...props, kind: 'Function' }
660
- }
661
-
662
- /**
663
- * Creates an `ArrowFunctionNode` representing a TypeScript arrow function.
664
- *
665
- * Mirrors the `Function.Arrow` component from `@kubb/renderer-jsx`.
666
- * The component's `children` are represented as `nodes`.
667
- *
668
- * @example Simple arrow function
669
- * ```ts
670
- * createArrowFunction({ name: 'getPet' })
671
- * // const getPet = () => { ... }
672
- * ```
673
- *
674
- * @example Single-line exported arrow function
675
- * ```ts
676
- * createArrowFunction({ name: 'double', export: true, params: 'n: number', singleLine: true })
677
- * // export const double = (n: number) => ...
678
- * ```
679
- *
680
- * @example Async arrow function with generics
681
- * ```ts
682
- * createArrowFunction({
683
- * name: 'fetchPet',
684
- * export: true,
685
- * async: true,
686
- * generics: ['T'],
687
- * params: 'id: string',
688
- * returnType: 'T',
689
- * })
690
- * // export const fetchPet = async <T>(id: string): Promise<T> => { ... }
691
- * ```
692
- */
693
- export function createArrowFunction(props: Omit<ArrowFunctionNode, 'kind'>): ArrowFunctionNode {
694
- return { ...props, kind: 'ArrowFunction' }
695
- }
696
-
697
- /**
698
- * Creates a {@link TextNode} representing a raw string fragment in the source output.
699
- *
700
- * Use this instead of bare strings when building `nodes` arrays so that every
701
- * entry in the array is a typed {@link CodeNode}.
702
- *
703
- * @example
704
- * ```ts
705
- * createText('return fetch(id)')
706
- * // { kind: 'Text', value: 'return fetch(id)' }
707
- * ```
708
- */
709
- export function createText(value: string): TextNode {
710
- return { value, kind: 'Text' }
711
- }
712
-
713
- /**
714
- * Creates a {@link BreakNode} representing a line break in the source output.
715
- *
716
- * Corresponds to `<br/>` in JSX components. Prints as an empty string which,
717
- * when joined with `\n` by `printNodes`, produces a blank line.
718
- *
719
- * @example
720
- * ```ts
721
- * createBreak()
722
- * // { kind: 'Break' }
723
- * ```
724
- */
725
- export function createBreak(): BreakNode {
726
- return { kind: 'Break' }
727
- }
728
-
729
- /**
730
- * Creates a {@link JsxNode} representing a raw JSX fragment in the source output.
731
- *
732
- * Use this to embed JSX markup (including fragments `<>…</>`) directly in generated code.
733
- *
734
- * @example
735
- * ```ts
736
- * createJsx('<>\n <a href={href}>Open</a>\n</>')
737
- * // { kind: 'Jsx', value: '<>\n <a href={href}>Open</a>\n</>' }
738
- * ```
739
- */
740
- export function createJsx(value: string): JsxNode {
741
- return { value, kind: 'Jsx' }
742
- }