@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/visitor.ts CHANGED
@@ -1,7 +1,44 @@
1
1
  import type { VisitorDepth } from './constants.ts'
2
2
  import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
3
- import { createParameter, createProperty } from './factory.ts'
4
- import type { InputNode, Node, OperationNode, OutputNode, ParameterNode, PropertyNode, ResponseNode, SchemaNode } from './nodes/index.ts'
3
+ import type { NodeDef } from './node.ts'
4
+ import type {
5
+ ContentNode,
6
+ InputNode,
7
+ Node,
8
+ NodeKind,
9
+ OperationNode,
10
+ OutputNode,
11
+ ParameterNode,
12
+ PropertyNode,
13
+ RequestBodyNode,
14
+ ResponseNode,
15
+ SchemaNode,
16
+ } from './nodes/index.ts'
17
+ import { nodeDefs } from './registry.ts'
18
+
19
+ /**
20
+ * Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
21
+ * Derived from each definition's `children`.
22
+ */
23
+ const VISITOR_KEYS = Object.fromEntries(nodeDefs.flatMap((def) => (def.children ? [[def.kind, def.children] as const] : []))) as Partial<
24
+ Record<NodeKind, ReadonlyArray<string>>
25
+ >
26
+
27
+ /**
28
+ * Maps a node kind to the matching visitor callback name. Derived from each
29
+ * definition's `visitorKey`.
30
+ */
31
+ const VISITOR_KEY_BY_KIND = Object.fromEntries(nodeDefs.flatMap((def) => (def.visitorKey ? [[def.kind, def.visitorKey] as const] : []))) as Partial<
32
+ Record<NodeKind, NonNullable<NodeDef['visitorKey']>>
33
+ >
34
+
35
+ /**
36
+ * Per-kind builders rerun after children are rebuilt. Derived from each
37
+ * definition's `rebuild` flag.
38
+ */
39
+ const nodeRebuilders = Object.fromEntries(
40
+ nodeDefs.flatMap((def) => (def.rebuild ? [[def.kind, def.create as unknown as (node: Node) => Node] as const] : [])),
41
+ ) as Partial<Record<NodeKind, (node: Node) => Node>>
5
42
 
6
43
  /**
7
44
  * Creates a small async concurrency limiter.
@@ -54,7 +91,9 @@ type ParentNodeMap = [
54
91
  [InputNode, undefined],
55
92
  [OutputNode, undefined],
56
93
  [OperationNode, InputNode],
57
- [SchemaNode, InputNode | OperationNode | SchemaNode | PropertyNode | ParameterNode | ResponseNode],
94
+ [RequestBodyNode, OperationNode],
95
+ [ContentNode, RequestBodyNode | ResponseNode],
96
+ [SchemaNode, InputNode | ContentNode | SchemaNode | PropertyNode | ParameterNode],
58
97
  [PropertyNode, SchemaNode],
59
98
  [ParameterNode, OperationNode],
60
99
  [ResponseNode, OperationNode],
@@ -63,8 +102,7 @@ type ParentNodeMap = [
63
102
  /**
64
103
  * Resolves the parent node type for a given AST node type.
65
104
  *
66
- * This is used by visitor context so `ctx.parent` is correctly typed
67
- * for each callback.
105
+ * Visitor context relies on this so `ctx.parent` is typed for each callback.
68
106
  *
69
107
  * @example
70
108
  * ```ts
@@ -115,29 +153,43 @@ export type VisitorContext<T extends Node = Node> = {
115
153
  }
116
154
 
117
155
  /**
118
- * Synchronous visitor used by `transform`.
156
+ * Synchronous visitor consumed by `transform`. Each optional callback runs
157
+ * for the matching node type. Return a new node to replace it, or `undefined`
158
+ * to leave it untouched.
119
159
  *
120
- * @example
160
+ * Plugins typically expose `transformer` so users can supply a `Visitor` that
161
+ * rewrites the AST before printing.
162
+ *
163
+ * @example Prefix every operationId
121
164
  * ```ts
122
165
  * const visitor: Visitor = {
123
166
  * operation(node) {
124
- * return { ...node, operationId: `x_${node.operationId}` }
167
+ * return { ...node, operationId: `api_${node.operationId}` }
168
+ * },
169
+ * }
170
+ * ```
171
+ *
172
+ * @example Strip schema descriptions
173
+ * ```ts
174
+ * const visitor: Visitor = {
175
+ * schema(node) {
176
+ * return { ...node, description: undefined }
125
177
  * },
126
178
  * }
127
179
  * ```
128
180
  */
129
181
  export type Visitor = {
130
- input?(node: InputNode, context: VisitorContext<InputNode>): void | InputNode
131
- output?(node: OutputNode, context: VisitorContext<OutputNode>): void | OutputNode
132
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): void | OperationNode
133
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): void | SchemaNode
134
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): void | PropertyNode
135
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): void | ParameterNode
136
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): void | ResponseNode
182
+ input?(node: InputNode, context: VisitorContext<InputNode>): undefined | null | InputNode
183
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): undefined | null | OutputNode
184
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): undefined | null | OperationNode
185
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): undefined | null | SchemaNode
186
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): undefined | null | PropertyNode
187
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): undefined | null | ParameterNode
188
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): undefined | null | ResponseNode
137
189
  }
138
190
 
139
191
  /**
140
- * Utility type for values that can be returned directly or asynchronously.
192
+ * A visitor callback result that may be sync or async.
141
193
  */
142
194
  type MaybePromise<T> = T | Promise<T>
143
195
 
@@ -153,14 +205,14 @@ type MaybePromise<T> = T | Promise<T>
153
205
  * }
154
206
  * ```
155
207
  */
156
- export type AsyncVisitor = {
157
- input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<void | InputNode>
158
- output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<void | OutputNode>
159
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<void | OperationNode>
160
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<void | SchemaNode>
161
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<void | PropertyNode>
162
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<void | ParameterNode>
163
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<void | ResponseNode>
208
+ type AsyncVisitor = {
209
+ input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<undefined | null | InputNode>
210
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<undefined | null | OutputNode>
211
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<undefined | null | OperationNode>
212
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<undefined | null | SchemaNode>
213
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<undefined | null | PropertyNode>
214
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<undefined | null | ParameterNode>
215
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<undefined | null | ResponseNode>
164
216
  }
165
217
 
166
218
  /**
@@ -175,14 +227,14 @@ export type AsyncVisitor = {
175
227
  * }
176
228
  * ```
177
229
  */
178
- export type CollectVisitor<T> = {
179
- input?(node: InputNode, context: VisitorContext<InputNode>): T | undefined
180
- output?(node: OutputNode, context: VisitorContext<OutputNode>): T | undefined
181
- operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | undefined
182
- schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | undefined
183
- property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | undefined
184
- parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | undefined
185
- response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | undefined
230
+ type CollectVisitor<T> = {
231
+ input?(node: InputNode, context: VisitorContext<InputNode>): T | null | undefined
232
+ output?(node: OutputNode, context: VisitorContext<OutputNode>): T | null | undefined
233
+ operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | null | undefined
234
+ schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | null | undefined
235
+ property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | null | undefined
236
+ parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | null | undefined
237
+ response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | null | undefined
186
238
  }
187
239
 
188
240
  /**
@@ -201,7 +253,7 @@ export type CollectVisitor<T> = {
201
253
  */
202
254
  export type TransformOptions = Visitor & {
203
255
  /**
204
- * Traversal depth (`'deep'` by default).
256
+ * Traversal depth.
205
257
  * @default 'deep'
206
258
  */
207
259
  depth?: VisitorDepth
@@ -221,7 +273,7 @@ export type TransformOptions = Visitor & {
221
273
  */
222
274
  export type WalkOptions = AsyncVisitor & {
223
275
  /**
224
- * Traversal depth (`'deep'` by default).
276
+ * Traversal depth.
225
277
  * @default 'deep'
226
278
  */
227
279
  depth?: VisitorDepth
@@ -242,7 +294,7 @@ export type WalkOptions = AsyncVisitor & {
242
294
  */
243
295
  export type CollectOptions<T> = CollectVisitor<T> & {
244
296
  /**
245
- * Traversal depth (`'deep'` by default).
297
+ * Traversal depth.
246
298
  * @default 'deep'
247
299
  */
248
300
  depth?: VisitorDepth
@@ -252,61 +304,70 @@ export type CollectOptions<T> = CollectVisitor<T> & {
252
304
  parent?: Node
253
305
  }
254
306
 
307
+ const visitorKeysByKind = VISITOR_KEYS as Record<string, ReadonlyArray<string> | undefined>
308
+
309
+ /**
310
+ * Returns `true` when `value` is an AST node (an object carrying a `kind`).
311
+ */
312
+ function isNode(value: unknown): value is Node {
313
+ return typeof value === 'object' && value !== null && 'kind' in value
314
+ }
315
+
255
316
  /**
256
- * Returns the immediate traversable children of `node`.
317
+ * Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
257
318
  *
258
- * For `Schema` nodes, children (`properties`, `items`, `members`, and non-boolean
259
- * `additionalProperties`) are only included
260
- * when `recurse` is `true`; shallow mode skips them.
319
+ * `Schema` children are only included when `recurse` is `true`. Shallow mode skips them.
261
320
  *
262
321
  * @example
263
322
  * ```ts
264
323
  * const children = getChildren(operationNode, true)
265
- * // returns parameters, requestBody schema (if present), and responses
324
+ * // returns parameters, the request body, and responses
266
325
  * ```
267
326
  */
268
- function getChildren(node: Node, recurse: boolean): Array<Node> {
269
- switch (node.kind) {
270
- case 'Input':
271
- return [...node.schemas, ...node.operations]
272
- case 'Output':
273
- return []
274
- case 'Operation':
275
- return [...node.parameters, ...(node.requestBody?.content?.flatMap((c) => (c.schema ? [c.schema] : [])) ?? []), ...node.responses]
276
- case 'Schema': {
277
- const children: Array<Node> = []
278
-
279
- if (!recurse) return []
280
-
281
- if ('properties' in node && node.properties.length > 0) children.push(...node.properties)
282
- if ('items' in node && node.items) children.push(...node.items)
283
- if ('members' in node && node.members) children.push(...node.members)
284
- if ('additionalProperties' in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties)
285
-
286
- return children
327
+ function* getChildren(node: Node, recurse: boolean): Generator<Node, void, undefined> {
328
+ if (node.kind === 'Schema' && !recurse) return
329
+
330
+ const keys = visitorKeysByKind[node.kind]
331
+ if (!keys) return
332
+
333
+ const record = node as unknown as Record<string, unknown>
334
+ for (const key of keys) {
335
+ const value = record[key]
336
+ if (Array.isArray(value)) {
337
+ for (const item of value) if (isNode(item)) yield item
338
+ } else if (isNode(value)) {
339
+ yield value
287
340
  }
288
- case 'Property':
289
- return [node.schema]
290
- case 'Parameter':
291
- return [node.schema]
292
- case 'Response':
293
- return node.schema ? [node.schema] : []
294
- case 'FunctionParameter':
295
- case 'ParameterGroup':
296
- case 'FunctionParameters':
297
- case 'Type':
298
- return []
299
- default:
300
- return []
301
341
  }
302
342
  }
303
343
 
304
344
  /**
305
- * Depth-first traversal for side effects. Visitor return values are ignored.
306
- * Sibling nodes at each level are visited concurrently up to `options.concurrency`
307
- * (default: `WALK_CONCURRENCY`).
345
+ * Runs the visitor callback that matches `node.kind` with the traversal
346
+ * context. The result is a replacement node, a collected value, or `undefined`
347
+ * when no callback is registered for the kind.
308
348
  *
309
- * @example
349
+ * Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
350
+ * in one place. `TResult` is the caller's expected return: the same node type
351
+ * for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
352
+ */
353
+ function applyVisitor<TResult>(node: Node, visitor: Visitor | AsyncVisitor | CollectVisitor<unknown>, parent: Node | undefined): TResult | null | undefined {
354
+ const key = VISITOR_KEY_BY_KIND[node.kind]
355
+ if (!key) return undefined
356
+
357
+ const fn = visitor[key] as ((node: Node, context: VisitorContext) => TResult | null | undefined) | undefined
358
+
359
+ return fn?.(node, { parent })
360
+ }
361
+
362
+ /**
363
+ * Async depth-first traversal for side effects. Visitor return values are
364
+ * ignored. Use `transform` when you want to rewrite nodes.
365
+ *
366
+ * Sibling nodes at each depth run concurrently up to `options.concurrency`
367
+ * (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
368
+ * work. Lower values reduce memory pressure.
369
+ *
370
+ * @example Log every operation
310
371
  * ```ts
311
372
  * await walk(root, {
312
373
  * operation(node) {
@@ -315,10 +376,9 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
315
376
  * })
316
377
  * ```
317
378
  *
318
- * @example
379
+ * @example Only visit the root node
319
380
  * ```ts
320
- * // Visit only the current node
321
- * await walk(root, { depth: 'shallow', root: () => {} })
381
+ * await walk(root, { depth: 'shallow', input: () => {} })
322
382
  * ```
323
383
  */
324
384
  export async function walk(node: Node, options: WalkOptions): Promise<void> {
@@ -329,55 +389,26 @@ export async function walk(node: Node, options: WalkOptions): Promise<void> {
329
389
  }
330
390
 
331
391
  async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
332
- switch (node.kind) {
333
- case 'Input':
334
- await limit(() => visitor.input?.(node, { parent: parent as ParentOf<InputNode> }))
335
- break
336
- case 'Output':
337
- await limit(() => visitor.output?.(node, { parent: parent as ParentOf<OutputNode> }))
338
- break
339
- case 'Operation':
340
- await limit(() =>
341
- visitor.operation?.(node, {
342
- parent: parent as ParentOf<OperationNode>,
343
- }),
344
- )
345
- break
346
- case 'Schema':
347
- await limit(() => visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> }))
348
- break
349
- case 'Property':
350
- await limit(() => visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> }))
351
- break
352
- case 'Parameter':
353
- await limit(() =>
354
- visitor.parameter?.(node, {
355
- parent: parent as ParentOf<ParameterNode>,
356
- }),
357
- )
358
- break
359
- case 'Response':
360
- await limit(() => visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> }))
361
- break
362
- case 'FunctionParameter':
363
- case 'ParameterGroup':
364
- case 'FunctionParameters':
365
- break
366
- }
392
+ await limit(() => applyVisitor(node, visitor, parent))
367
393
 
368
- const children = getChildren(node, recurse)
369
- for (const child of children) {
370
- await _walk(child, visitor, recurse, limit, node)
371
- }
394
+ // Visit siblings concurrently and let the shared `limit` cap how many callbacks
395
+ // run at once. Awaiting each child sequentially here would serialize the whole
396
+ // traversal and make `concurrency` inert. Every visitor callback would run one
397
+ // at a time regardless of the limit.
398
+ const children = Array.from(getChildren(node, recurse))
399
+ if (children.length === 0) return
400
+
401
+ await Promise.all(children.map((child) => _walk(child, visitor, recurse, limit, node)))
372
402
  }
373
403
 
374
404
  /**
375
- * Runs a depth-first immutable transform.
405
+ * Synchronous depth-first transform. Each visitor callback can return a
406
+ * replacement node. Returning `undefined` keeps the original.
376
407
  *
377
- * If a visitor returns a node, it replaces the current node.
378
- * If it returns `undefined`, the current node stays the same.
408
+ * The original tree is never mutated, a new tree is returned. Pass
409
+ * `depth: 'shallow'` to skip recursion into children.
379
410
  *
380
- * @example
411
+ * @example Prefix every operationId
381
412
  * ```ts
382
413
  * const next = transform(root, {
383
414
  * operation(node) {
@@ -386,10 +417,12 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
386
417
  * })
387
418
  * ```
388
419
  *
389
- * @example
420
+ * @example Replace only the root node
390
421
  * ```ts
391
- * // Shallow mode: only transform the input node
392
- * const next = transform(root, { depth: 'shallow', root: (node) => node })
422
+ * const next = transform(root, {
423
+ * depth: 'shallow',
424
+ * input: (node) => ({ ...node, meta: { ...node.meta, title: 'Rewritten' } }),
425
+ * })
393
426
  * ```
394
427
  */
395
428
  export function transform(node: InputNode, options: TransformOptions): InputNode
@@ -404,189 +437,94 @@ export function transform(node: Node, options: TransformOptions): Node {
404
437
  const { depth, parent, ...visitor } = options
405
438
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
406
439
 
407
- switch (node.kind) {
408
- case 'Input': {
409
- let input = node
410
- const replaced = visitor.input?.(input, {
411
- parent: parent as ParentOf<InputNode>,
412
- })
413
- if (replaced) input = replaced
414
-
415
- return {
416
- ...input,
417
- schemas: input.schemas.map((s) => transform(s, { ...options, parent: input })),
418
- operations: input.operations.map((op) => transform(op, { ...options, parent: input })),
419
- }
420
- }
421
- case 'Output': {
422
- let output = node
423
- const replaced = visitor.output?.(output, {
424
- parent: parent as ParentOf<OutputNode>,
425
- })
426
- if (replaced) output = replaced
440
+ const visited = applyVisitor<Node>(node, visitor, parent) ?? node
441
+ const rebuilt = transformChildren(visited, options, recurse)
427
442
 
428
- return output
429
- }
430
- case 'Operation': {
431
- let op = node
432
- const replaced = visitor.operation?.(op, {
433
- parent: parent as ParentOf<OperationNode>,
434
- })
435
- if (replaced) op = replaced
436
-
437
- return {
438
- ...op,
439
- parameters: op.parameters.map((p) => transform(p, { ...options, parent: op })),
440
- requestBody: op.requestBody
441
- ? {
442
- ...op.requestBody,
443
- content: op.requestBody.content?.map((c) => ({
444
- ...c,
445
- schema: c.schema ? transform(c.schema, { ...options, parent: op }) : undefined,
446
- })),
447
- }
448
- : undefined,
449
- responses: op.responses.map((r) => transform(r, { ...options, parent: op })),
450
- }
451
- }
452
- case 'Schema': {
453
- let schema = node
454
- const replaced = visitor.schema?.(schema, {
455
- parent: parent as ParentOf<SchemaNode>,
456
- })
457
- if (replaced) schema = replaced
458
-
459
- const childOptions = { ...options, parent: schema }
460
-
461
- return {
462
- ...schema,
463
- ...('properties' in schema && recurse
464
- ? {
465
- properties: schema.properties.map((p) => transform(p, childOptions)),
466
- }
467
- : {}),
468
- ...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {}),
469
- ...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {}),
470
- ...('additionalProperties' in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true
471
- ? {
472
- additionalProperties: transform(schema.additionalProperties, childOptions),
473
- }
474
- : {}),
475
- } as SchemaNode
476
- }
477
- case 'Property': {
478
- let prop = node
479
- const replaced = visitor.property?.(prop, {
480
- parent: parent as ParentOf<PropertyNode>,
481
- })
482
- if (replaced) prop = replaced
443
+ // Structural sharing: when the visitor and child rebuild both left this node
444
+ // untouched, return the original reference so callers can detect "nothing
445
+ // changed" by identity and ancestors can avoid reallocating.
446
+ if (rebuilt === node) return node
483
447
 
484
- return createProperty({
485
- ...prop,
486
- schema: transform(prop.schema, { ...options, parent: prop }),
487
- })
488
- }
489
- case 'Parameter': {
490
- let param = node
491
- const replaced = visitor.parameter?.(param, {
492
- parent: parent as ParentOf<ParameterNode>,
493
- })
494
- if (replaced) param = replaced
448
+ const rebuild = nodeRebuilders[rebuilt.kind]
449
+ return rebuild ? rebuild(rebuilt) : rebuilt
450
+ }
495
451
 
496
- return createParameter({
497
- ...param,
498
- schema: transform(param.schema, { ...options, parent: param }),
499
- })
500
- }
501
- case 'Response': {
502
- let response = node
503
- const replaced = visitor.response?.(response, {
504
- parent: parent as ParentOf<ResponseNode>,
452
+ /**
453
+ * Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
454
+ * each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
455
+ * `Schema` children are skipped in shallow mode.
456
+ */
457
+ function transformChildren(node: Node, options: TransformOptions, recurse: boolean): Node {
458
+ if (node.kind === 'Schema' && !recurse) return node
459
+
460
+ const keys = visitorKeysByKind[node.kind]
461
+ if (!keys) return node
462
+
463
+ const record = node as unknown as Record<string, unknown>
464
+ const childOptions = { ...options, parent: node }
465
+ let updates: Record<string, unknown> | undefined
466
+
467
+ for (const key of keys) {
468
+ if (!(key in record)) continue
469
+ const value = record[key]
470
+ if (Array.isArray(value)) {
471
+ let changed = false
472
+ const mapped = value.map((item) => {
473
+ if (!isNode(item)) return item
474
+ const next = transform(item, childOptions)
475
+ if (next !== item) changed = true
476
+ return next
505
477
  })
506
- if (replaced) response = replaced
507
-
508
- return {
509
- ...response,
510
- schema: transform(response.schema, { ...options, parent: response }),
511
- }
478
+ if (changed) (updates ??= {})[key] = mapped
479
+ } else if (isNode(value)) {
480
+ const next = transform(value, childOptions)
481
+ if (next !== value) (updates ??= {})[key] = next
512
482
  }
513
- case 'FunctionParameter':
514
- case 'ParameterGroup':
515
- case 'FunctionParameters':
516
- case 'Type':
517
- return node
518
- default:
519
- return node
520
483
  }
484
+
485
+ return updates ? ({ ...node, ...updates } as Node) : node
521
486
  }
522
487
  /**
523
- * Runs a depth-first synchronous collection pass.
524
- *
525
- * Non-`undefined` values returned by visitor callbacks are appended to the result.
488
+ * Lazy depth-first collection pass. Yields every non-null value returned by
489
+ * the visitor callbacks. Use `collect` for the eager array form.
526
490
  *
527
- * @example
491
+ * @example Collect every operationId
528
492
  * ```ts
529
- * const ids = collect(root, {
493
+ * const ids: string[] = []
494
+ * for (const id of collectLazy<string>(root, {
530
495
  * operation(node) {
531
496
  * return node.operationId
532
497
  * },
533
- * })
534
- * ```
535
- *
536
- * @example
537
- * ```ts
538
- * // Collect from only the current node
539
- * const values = collect(root, { depth: 'shallow', root: () => 'root' })
498
+ * })) {
499
+ * ids.push(id)
500
+ * }
540
501
  * ```
541
502
  */
542
- export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
503
+ export function* collectLazy<T>(node: Node, options: CollectOptions<T>): Generator<T, void, undefined> {
543
504
  const { depth, parent, ...visitor } = options
544
505
  const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
545
- const results: Array<T> = []
546
-
547
- let v: T | undefined
548
- switch (node.kind) {
549
- case 'Input':
550
- v = visitor.input?.(node, { parent: parent as ParentOf<InputNode> })
551
- break
552
- case 'Output':
553
- v = visitor.output?.(node, { parent: parent as ParentOf<OutputNode> })
554
- break
555
- case 'Operation':
556
- v = visitor.operation?.(node, {
557
- parent: parent as ParentOf<OperationNode>,
558
- })
559
- break
560
- case 'Schema':
561
- v = visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> })
562
- break
563
- case 'Property':
564
- v = visitor.property?.(node, {
565
- parent: parent as ParentOf<PropertyNode>,
566
- })
567
- break
568
- case 'Parameter':
569
- v = visitor.parameter?.(node, {
570
- parent: parent as ParentOf<ParameterNode>,
571
- })
572
- break
573
- case 'Response':
574
- v = visitor.response?.(node, {
575
- parent: parent as ParentOf<ResponseNode>,
576
- })
577
- break
578
- case 'FunctionParameter':
579
- case 'ParameterGroup':
580
- case 'FunctionParameters':
581
- break
582
- }
583
- if (v !== undefined) results.push(v)
506
+
507
+ const v = applyVisitor<T>(node, visitor, parent)
508
+ if (v != null) yield v
584
509
 
585
510
  for (const child of getChildren(node, recurse)) {
586
- for (const item of collect(child, { ...options, parent: node })) {
587
- results.push(item)
588
- }
511
+ yield* collectLazy(child, { ...options, parent: node })
589
512
  }
513
+ }
590
514
 
591
- return results
515
+ /**
516
+ * Eager depth-first collection pass. Gathers every non-null value the visitor
517
+ * callbacks return into an array.
518
+ *
519
+ * @example Collect every operationId
520
+ * ```ts
521
+ * const ids = collect<string>(root, {
522
+ * operation(node) {
523
+ * return node.operationId
524
+ * },
525
+ * })
526
+ * ```
527
+ */
528
+ export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
529
+ return Array.from(collectLazy(node, options))
592
530
  }
@@ -1,8 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __name = (target, value) => __defProp(target, "name", {
4
- value,
5
- configurable: true
6
- });
7
- //#endregion
8
- export { __name as t };