@kubb/ast 5.0.0-beta.3 → 5.0.0-beta.31
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/README.md +1 -1
- package/dist/index.cjs +694 -331
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1433 -1007
- package/dist/index.js +682 -332
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/dedupe.ts +202 -0
- package/src/dialect.ts +64 -0
- package/src/dispatch.ts +53 -0
- package/src/factory.ts +127 -11
- package/src/guards.ts +18 -3
- package/src/index.ts +11 -3
- package/src/infer.ts +16 -5
- package/src/nodes/base.ts +2 -0
- package/src/nodes/code.ts +21 -21
- package/src/nodes/content.ts +37 -0
- package/src/nodes/file.ts +16 -14
- package/src/nodes/index.ts +7 -3
- package/src/nodes/operation.ts +98 -62
- package/src/nodes/response.ts +21 -14
- package/src/nodes/root.ts +72 -10
- package/src/nodes/schema.ts +9 -3
- package/src/printer.ts +34 -28
- package/src/refs.ts +4 -2
- package/src/resolvers.ts +4 -4
- package/src/signature.ts +135 -0
- package/src/transformers.ts +20 -15
- package/src/types.ts +8 -0
- package/src/utils.ts +109 -68
- package/src/visitor.ts +229 -275
package/src/visitor.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { VisitorDepth } from './constants.ts'
|
|
2
2
|
import { visitorDepths, WALK_CONCURRENCY } from './constants.ts'
|
|
3
3
|
import { createParameter, createProperty } from './factory.ts'
|
|
4
|
-
import type {
|
|
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'
|
|
5
17
|
|
|
6
18
|
/**
|
|
7
19
|
* Creates a small async concurrency limiter.
|
|
@@ -54,7 +66,9 @@ type ParentNodeMap = [
|
|
|
54
66
|
[InputNode, undefined],
|
|
55
67
|
[OutputNode, undefined],
|
|
56
68
|
[OperationNode, InputNode],
|
|
57
|
-
[
|
|
69
|
+
[RequestBodyNode, OperationNode],
|
|
70
|
+
[ContentNode, RequestBodyNode | ResponseNode],
|
|
71
|
+
[SchemaNode, InputNode | ContentNode | SchemaNode | PropertyNode | ParameterNode],
|
|
58
72
|
[PropertyNode, SchemaNode],
|
|
59
73
|
[ParameterNode, OperationNode],
|
|
60
74
|
[ResponseNode, OperationNode],
|
|
@@ -115,25 +129,40 @@ export type VisitorContext<T extends Node = Node> = {
|
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
/**
|
|
118
|
-
* Synchronous visitor
|
|
132
|
+
* Synchronous visitor consumed by `transform`. Each optional callback runs
|
|
133
|
+
* for the matching node type. Return a new node to replace it, or `undefined`
|
|
134
|
+
* to leave it untouched.
|
|
119
135
|
*
|
|
120
|
-
*
|
|
136
|
+
* Plugins typically expose `transformer` so users can supply a `Visitor` that
|
|
137
|
+
* rewrites operation IDs, drops descriptions, or otherwise tweaks the AST
|
|
138
|
+
* before printing.
|
|
139
|
+
*
|
|
140
|
+
* @example Prefix every operationId
|
|
121
141
|
* ```ts
|
|
122
142
|
* const visitor: Visitor = {
|
|
123
143
|
* operation(node) {
|
|
124
|
-
* return { ...node, operationId: `
|
|
144
|
+
* return { ...node, operationId: `api_${node.operationId}` }
|
|
145
|
+
* },
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @example Strip schema descriptions
|
|
150
|
+
* ```ts
|
|
151
|
+
* const visitor: Visitor = {
|
|
152
|
+
* schema(node) {
|
|
153
|
+
* return { ...node, description: undefined }
|
|
125
154
|
* },
|
|
126
155
|
* }
|
|
127
156
|
* ```
|
|
128
157
|
*/
|
|
129
158
|
export type Visitor = {
|
|
130
|
-
input?(node: InputNode, context: VisitorContext<InputNode>):
|
|
131
|
-
output?(node: OutputNode, context: VisitorContext<OutputNode>):
|
|
132
|
-
operation?(node: OperationNode, context: VisitorContext<OperationNode>):
|
|
133
|
-
schema?(node: SchemaNode, context: VisitorContext<SchemaNode>):
|
|
134
|
-
property?(node: PropertyNode, context: VisitorContext<PropertyNode>):
|
|
135
|
-
parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>):
|
|
136
|
-
response?(node: ResponseNode, context: VisitorContext<ResponseNode>):
|
|
159
|
+
input?(node: InputNode, context: VisitorContext<InputNode>): undefined | null | InputNode
|
|
160
|
+
output?(node: OutputNode, context: VisitorContext<OutputNode>): undefined | null | OutputNode
|
|
161
|
+
operation?(node: OperationNode, context: VisitorContext<OperationNode>): undefined | null | OperationNode
|
|
162
|
+
schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): undefined | null | SchemaNode
|
|
163
|
+
property?(node: PropertyNode, context: VisitorContext<PropertyNode>): undefined | null | PropertyNode
|
|
164
|
+
parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): undefined | null | ParameterNode
|
|
165
|
+
response?(node: ResponseNode, context: VisitorContext<ResponseNode>): undefined | null | ResponseNode
|
|
137
166
|
}
|
|
138
167
|
|
|
139
168
|
/**
|
|
@@ -154,13 +183,13 @@ type MaybePromise<T> = T | Promise<T>
|
|
|
154
183
|
* ```
|
|
155
184
|
*/
|
|
156
185
|
export type AsyncVisitor = {
|
|
157
|
-
input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<
|
|
158
|
-
output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<
|
|
159
|
-
operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<
|
|
160
|
-
schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<
|
|
161
|
-
property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<
|
|
162
|
-
parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<
|
|
163
|
-
response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<
|
|
186
|
+
input?(node: InputNode, context: VisitorContext<InputNode>): MaybePromise<undefined | null | InputNode>
|
|
187
|
+
output?(node: OutputNode, context: VisitorContext<OutputNode>): MaybePromise<undefined | null | OutputNode>
|
|
188
|
+
operation?(node: OperationNode, context: VisitorContext<OperationNode>): MaybePromise<undefined | null | OperationNode>
|
|
189
|
+
schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): MaybePromise<undefined | null | SchemaNode>
|
|
190
|
+
property?(node: PropertyNode, context: VisitorContext<PropertyNode>): MaybePromise<undefined | null | PropertyNode>
|
|
191
|
+
parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): MaybePromise<undefined | null | ParameterNode>
|
|
192
|
+
response?(node: ResponseNode, context: VisitorContext<ResponseNode>): MaybePromise<undefined | null | ResponseNode>
|
|
164
193
|
}
|
|
165
194
|
|
|
166
195
|
/**
|
|
@@ -176,13 +205,13 @@ export type AsyncVisitor = {
|
|
|
176
205
|
* ```
|
|
177
206
|
*/
|
|
178
207
|
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
|
|
208
|
+
input?(node: InputNode, context: VisitorContext<InputNode>): T | null | undefined
|
|
209
|
+
output?(node: OutputNode, context: VisitorContext<OutputNode>): T | null | undefined
|
|
210
|
+
operation?(node: OperationNode, context: VisitorContext<OperationNode>): T | null | undefined
|
|
211
|
+
schema?(node: SchemaNode, context: VisitorContext<SchemaNode>): T | null | undefined
|
|
212
|
+
property?(node: PropertyNode, context: VisitorContext<PropertyNode>): T | null | undefined
|
|
213
|
+
parameter?(node: ParameterNode, context: VisitorContext<ParameterNode>): T | null | undefined
|
|
214
|
+
response?(node: ResponseNode, context: VisitorContext<ResponseNode>): T | null | undefined
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
/**
|
|
@@ -253,60 +282,102 @@ export type CollectOptions<T> = CollectVisitor<T> & {
|
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
/**
|
|
256
|
-
*
|
|
285
|
+
* Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
|
|
257
286
|
*
|
|
258
|
-
*
|
|
259
|
-
* `additionalProperties`)
|
|
260
|
-
*
|
|
287
|
+
* Each listed property holds a child node, an array of child nodes, or — for
|
|
288
|
+
* `additionalProperties` — a node or the literal `true` (skipped). Every value
|
|
289
|
+
* in a child slot is a node, so one table drives both `getChildren` and `transform`.
|
|
290
|
+
*/
|
|
291
|
+
const VISITOR_KEYS = {
|
|
292
|
+
Input: ['schemas', 'operations'],
|
|
293
|
+
Operation: ['parameters', 'requestBody', 'responses'],
|
|
294
|
+
RequestBody: ['content'],
|
|
295
|
+
Content: ['schema'],
|
|
296
|
+
Response: ['content'],
|
|
297
|
+
Schema: ['properties', 'items', 'members', 'additionalProperties'],
|
|
298
|
+
Property: ['schema'],
|
|
299
|
+
Parameter: ['schema'],
|
|
300
|
+
} as const satisfies Partial<Record<NodeKind, ReadonlyArray<string>>>
|
|
301
|
+
|
|
302
|
+
const visitorKeysByKind = VISITOR_KEYS as Record<string, ReadonlyArray<string> | undefined>
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Returns `true` when `value` is an AST node (an object carrying a `kind`).
|
|
306
|
+
*/
|
|
307
|
+
function isNode(value: unknown): value is Node {
|
|
308
|
+
return typeof value === 'object' && value !== null && 'kind' in value
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Returns the immediate traversable children of `node` based on {@link VISITOR_KEYS}.
|
|
313
|
+
*
|
|
314
|
+
* `Schema` children are only included when `recurse` is `true`; shallow mode skips them.
|
|
261
315
|
*
|
|
262
316
|
* @example
|
|
263
317
|
* ```ts
|
|
264
318
|
* const children = getChildren(operationNode, true)
|
|
265
|
-
* // returns parameters,
|
|
319
|
+
* // returns parameters, the request body, and responses
|
|
266
320
|
* ```
|
|
267
321
|
*/
|
|
268
|
-
function getChildren(node: Node, recurse: boolean):
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
322
|
+
function* getChildren(node: Node, recurse: boolean): Generator<Node, void, undefined> {
|
|
323
|
+
if (node.kind === 'Schema' && !recurse) return
|
|
324
|
+
|
|
325
|
+
const keys = visitorKeysByKind[node.kind]
|
|
326
|
+
if (!keys) return
|
|
327
|
+
|
|
328
|
+
const record = node as unknown as Record<string, unknown>
|
|
329
|
+
for (const key of keys) {
|
|
330
|
+
const value = record[key]
|
|
331
|
+
if (Array.isArray(value)) {
|
|
332
|
+
for (const item of value) if (isNode(item)) yield item
|
|
333
|
+
} else if (isNode(value)) {
|
|
334
|
+
yield value
|
|
287
335
|
}
|
|
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
336
|
}
|
|
302
337
|
}
|
|
303
338
|
|
|
304
339
|
/**
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
340
|
+
* Maps a node `kind` to the matching visitor callback name. Only the seven
|
|
341
|
+
* traversable node kinds have an entry; every other kind resolves to
|
|
342
|
+
* `undefined` and is skipped.
|
|
343
|
+
*/
|
|
344
|
+
const VISITOR_KEY_BY_KIND: Partial<Record<NodeKind, keyof Visitor>> = {
|
|
345
|
+
Input: 'input',
|
|
346
|
+
Output: 'output',
|
|
347
|
+
Operation: 'operation',
|
|
348
|
+
Schema: 'schema',
|
|
349
|
+
Property: 'property',
|
|
350
|
+
Parameter: 'parameter',
|
|
351
|
+
Response: 'response',
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Invokes the visitor callback that matches `node.kind`, passing the traversal
|
|
356
|
+
* context. Returns the callback's result (a replacement node, a collected
|
|
357
|
+
* value, or `undefined` when no callback is registered for the kind).
|
|
308
358
|
*
|
|
309
|
-
*
|
|
359
|
+
* Shared by `walk`, `transform`, and `collectLazy` so node-kind dispatch lives
|
|
360
|
+
* in one place. `TResult` is the caller's expected return: the same node type
|
|
361
|
+
* for `transform`, the collected value type for `collectLazy`, ignored for `walk`.
|
|
362
|
+
*/
|
|
363
|
+
function applyVisitor<TResult>(node: Node, visitor: Visitor | AsyncVisitor | CollectVisitor<unknown>, parent: Node | undefined): TResult | null | undefined {
|
|
364
|
+
const key = VISITOR_KEY_BY_KIND[node.kind]
|
|
365
|
+
if (!key) return undefined
|
|
366
|
+
|
|
367
|
+
const fn = visitor[key] as ((node: Node, context: VisitorContext) => TResult | null | undefined) | undefined
|
|
368
|
+
|
|
369
|
+
return fn?.(node, { parent })
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Async depth-first traversal for side effects. Visitor return values are
|
|
374
|
+
* ignored. Use `transform` when you want to rewrite nodes.
|
|
375
|
+
*
|
|
376
|
+
* Sibling nodes at each depth run concurrently up to `options.concurrency`
|
|
377
|
+
* (defaults to `WALK_CONCURRENCY`). Higher values overlap I/O-bound visitor
|
|
378
|
+
* work; lower values reduce memory pressure.
|
|
379
|
+
*
|
|
380
|
+
* @example Log every operation
|
|
310
381
|
* ```ts
|
|
311
382
|
* await walk(root, {
|
|
312
383
|
* operation(node) {
|
|
@@ -315,10 +386,9 @@ function getChildren(node: Node, recurse: boolean): Array<Node> {
|
|
|
315
386
|
* })
|
|
316
387
|
* ```
|
|
317
388
|
*
|
|
318
|
-
* @example
|
|
389
|
+
* @example Only visit the root node
|
|
319
390
|
* ```ts
|
|
320
|
-
*
|
|
321
|
-
* await walk(root, { depth: 'shallow', root: () => {} })
|
|
391
|
+
* await walk(root, { depth: 'shallow', input: () => {} })
|
|
322
392
|
* ```
|
|
323
393
|
*/
|
|
324
394
|
export async function walk(node: Node, options: WalkOptions): Promise<void> {
|
|
@@ -329,41 +399,7 @@ export async function walk(node: Node, options: WalkOptions): Promise<void> {
|
|
|
329
399
|
}
|
|
330
400
|
|
|
331
401
|
async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
|
|
332
|
-
|
|
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
|
-
}
|
|
402
|
+
await limit(() => applyVisitor(node, visitor, parent))
|
|
367
403
|
|
|
368
404
|
const children = getChildren(node, recurse)
|
|
369
405
|
for (const child of children) {
|
|
@@ -372,12 +408,13 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
|
|
|
372
408
|
}
|
|
373
409
|
|
|
374
410
|
/**
|
|
375
|
-
*
|
|
411
|
+
* Synchronous depth-first transform. Each visitor callback gets a chance to
|
|
412
|
+
* return a replacement node; `undefined` keeps the original.
|
|
376
413
|
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
414
|
+
* The transform is immutable. The original tree is not mutated; a new tree
|
|
415
|
+
* is returned. Use `depth: 'shallow'` to skip recursion into children.
|
|
379
416
|
*
|
|
380
|
-
* @example
|
|
417
|
+
* @example Prefix every operationId
|
|
381
418
|
* ```ts
|
|
382
419
|
* const next = transform(root, {
|
|
383
420
|
* operation(node) {
|
|
@@ -386,10 +423,12 @@ async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit:
|
|
|
386
423
|
* })
|
|
387
424
|
* ```
|
|
388
425
|
*
|
|
389
|
-
* @example
|
|
426
|
+
* @example Replace only the root node
|
|
390
427
|
* ```ts
|
|
391
|
-
*
|
|
392
|
-
*
|
|
428
|
+
* const next = transform(root, {
|
|
429
|
+
* depth: 'shallow',
|
|
430
|
+
* input: (node) => ({ ...node, meta: { ...node.meta, title: 'Rewritten' } }),
|
|
431
|
+
* })
|
|
393
432
|
* ```
|
|
394
433
|
*/
|
|
395
434
|
export function transform(node: InputNode, options: TransformOptions): InputNode
|
|
@@ -404,189 +443,104 @@ export function transform(node: Node, options: TransformOptions): Node {
|
|
|
404
443
|
const { depth, parent, ...visitor } = options
|
|
405
444
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
|
|
406
445
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
let input = node
|
|
410
|
-
const replaced = visitor.input?.(input, {
|
|
411
|
-
parent: parent as ParentOf<InputNode>,
|
|
412
|
-
})
|
|
413
|
-
if (replaced) input = replaced
|
|
446
|
+
const visited = applyVisitor<Node>(node, visitor, parent) ?? node
|
|
447
|
+
const rebuilt = transformChildren(visited, options, recurse)
|
|
414
448
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
|
449
|
+
// Structural sharing: when the visitor and child rebuild both left this node
|
|
450
|
+
// untouched, return the original reference so callers can detect "nothing
|
|
451
|
+
// changed" by identity and ancestors can avoid reallocating.
|
|
452
|
+
if (rebuilt === node) return node
|
|
427
453
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
|
454
|
+
const finalize = nodeFinalizers[rebuilt.kind]
|
|
455
|
+
return finalize ? finalize(rebuilt) : rebuilt
|
|
456
|
+
}
|
|
483
457
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
})
|
|
494
|
-
if (replaced) param = replaced
|
|
458
|
+
/**
|
|
459
|
+
* Per-kind builders rerun after children are rebuilt. `Property`/`Parameter`
|
|
460
|
+
* resync schema optionality against their `required` flag once the schema may
|
|
461
|
+
* have changed.
|
|
462
|
+
*/
|
|
463
|
+
const nodeFinalizers: Partial<Record<NodeKind, (node: Node) => Node>> = {
|
|
464
|
+
Property: (node) => createProperty(node as PropertyNode),
|
|
465
|
+
Parameter: (node) => createParameter(node as ParameterNode),
|
|
466
|
+
}
|
|
495
467
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
468
|
+
/**
|
|
469
|
+
* Immutably rebuilds a node's children using {@link VISITOR_KEYS}, transforming
|
|
470
|
+
* each child node and leaving non-node values (e.g. `additionalProperties: true`) intact.
|
|
471
|
+
* `Schema` children are skipped in shallow mode.
|
|
472
|
+
*/
|
|
473
|
+
function transformChildren(node: Node, options: TransformOptions, recurse: boolean): Node {
|
|
474
|
+
if (node.kind === 'Schema' && !recurse) return node
|
|
475
|
+
|
|
476
|
+
const keys = visitorKeysByKind[node.kind]
|
|
477
|
+
if (!keys) return node
|
|
478
|
+
|
|
479
|
+
const record = node as unknown as Record<string, unknown>
|
|
480
|
+
const childOptions = { ...options, parent: node }
|
|
481
|
+
let updates: Record<string, unknown> | undefined
|
|
482
|
+
|
|
483
|
+
for (const key of keys) {
|
|
484
|
+
if (!(key in record)) continue
|
|
485
|
+
const value = record[key]
|
|
486
|
+
if (Array.isArray(value)) {
|
|
487
|
+
let changed = false
|
|
488
|
+
const mapped = value.map((item) => {
|
|
489
|
+
if (!isNode(item)) return item
|
|
490
|
+
const next = transform(item, childOptions)
|
|
491
|
+
if (next !== item) changed = true
|
|
492
|
+
return next
|
|
505
493
|
})
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
schema: transform(response.schema, { ...options, parent: response }),
|
|
511
|
-
}
|
|
494
|
+
if (changed) (updates ??= {})[key] = mapped
|
|
495
|
+
} else if (isNode(value)) {
|
|
496
|
+
const next = transform(value, childOptions)
|
|
497
|
+
if (next !== value) (updates ??= {})[key] = next
|
|
512
498
|
}
|
|
513
|
-
case 'FunctionParameter':
|
|
514
|
-
case 'ParameterGroup':
|
|
515
|
-
case 'FunctionParameters':
|
|
516
|
-
case 'Type':
|
|
517
|
-
return node
|
|
518
|
-
default:
|
|
519
|
-
return node
|
|
520
499
|
}
|
|
500
|
+
|
|
501
|
+
return updates ? ({ ...node, ...updates } as Node) : node
|
|
521
502
|
}
|
|
522
503
|
/**
|
|
523
|
-
*
|
|
524
|
-
*
|
|
525
|
-
* Non-`undefined` values returned by visitor callbacks are appended to the result.
|
|
504
|
+
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
505
|
+
* the visitor callbacks. Use `collect` for the eager array form.
|
|
526
506
|
*
|
|
527
|
-
* @example
|
|
507
|
+
* @example Collect every operationId
|
|
528
508
|
* ```ts
|
|
529
|
-
* const ids =
|
|
509
|
+
* const ids: string[] = []
|
|
510
|
+
* for (const id of collectLazy<string>(root, {
|
|
530
511
|
* operation(node) {
|
|
531
512
|
* return node.operationId
|
|
532
513
|
* },
|
|
533
|
-
* })
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* @example
|
|
537
|
-
* ```ts
|
|
538
|
-
* // Collect from only the current node
|
|
539
|
-
* const values = collect(root, { depth: 'shallow', root: () => 'root' })
|
|
514
|
+
* })) {
|
|
515
|
+
* ids.push(id)
|
|
516
|
+
* }
|
|
540
517
|
* ```
|
|
541
518
|
*/
|
|
542
|
-
export function
|
|
519
|
+
export function* collectLazy<T>(node: Node, options: CollectOptions<T>): Generator<T, void, undefined> {
|
|
543
520
|
const { depth, parent, ...visitor } = options
|
|
544
521
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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)
|
|
522
|
+
|
|
523
|
+
const v = applyVisitor<T>(node, visitor, parent)
|
|
524
|
+
if (v != null) yield v
|
|
584
525
|
|
|
585
526
|
for (const child of getChildren(node, recurse)) {
|
|
586
|
-
|
|
587
|
-
results.push(item)
|
|
588
|
-
}
|
|
527
|
+
yield* collectLazy(child, { ...options, parent: node })
|
|
589
528
|
}
|
|
529
|
+
}
|
|
590
530
|
|
|
591
|
-
|
|
531
|
+
/**
|
|
532
|
+
* Eager depth-first collection pass. Returns an array of every non-null value
|
|
533
|
+
* the visitor callbacks return.
|
|
534
|
+
*
|
|
535
|
+
* @example Collect every operationId
|
|
536
|
+
* ```ts
|
|
537
|
+
* const ids = collect<string>(root, {
|
|
538
|
+
* operation(node) {
|
|
539
|
+
* return node.operationId
|
|
540
|
+
* },
|
|
541
|
+
* })
|
|
542
|
+
* ```
|
|
543
|
+
*/
|
|
544
|
+
export function collect<T>(node: Node, options: CollectOptions<T>): Array<T> {
|
|
545
|
+
return Array.from(collectLazy(node, options))
|
|
592
546
|
}
|