@kubb/ast 5.0.0-beta.29 → 5.0.0-beta.30
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/index.cjs +256 -211
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1041 -837
- package/dist/index.js +251 -212
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dialect.ts +64 -0
- package/src/dispatch.ts +53 -0
- package/src/factory.ts +93 -6
- package/src/guards.ts +15 -0
- package/src/index.ts +6 -1
- package/src/nodes/base.ts +2 -0
- package/src/nodes/content.ts +37 -0
- package/src/nodes/index.ts +6 -2
- package/src/nodes/operation.ts +98 -62
- package/src/nodes/response.ts +2 -16
- package/src/types.ts +6 -0
- package/src/utils.ts +13 -6
- package/src/visitor.ts +140 -174
package/src/utils.ts
CHANGED
|
@@ -594,6 +594,17 @@ export function combineSources(sources: Array<SourceNode>): Array<SourceNode> {
|
|
|
594
594
|
return [...seen.values()]
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
599
|
+
*
|
|
600
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
601
|
+
*/
|
|
602
|
+
function mergeNameArrays<TName>(existing: Array<TName>, incoming: Array<TName>): Array<TName> {
|
|
603
|
+
const merged = new Set(existing)
|
|
604
|
+
for (const name of incoming) merged.add(name)
|
|
605
|
+
return [...merged]
|
|
606
|
+
}
|
|
607
|
+
|
|
597
608
|
/**
|
|
598
609
|
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
599
610
|
*
|
|
@@ -621,9 +632,7 @@ export function combineExports(exports: Array<ExportNode>): Array<ExportNode> {
|
|
|
621
632
|
const existing = namedByPath.get(key)
|
|
622
633
|
|
|
623
634
|
if (existing && Array.isArray(existing.name)) {
|
|
624
|
-
|
|
625
|
-
for (const n of name) merged.add(n)
|
|
626
|
-
existing.name = [...merged]
|
|
635
|
+
existing.name = mergeNameArrays(existing.name, name)
|
|
627
636
|
} else {
|
|
628
637
|
const newItem: ExportNode = { ...curr, name: [...new Set(name)] }
|
|
629
638
|
result.push(newItem)
|
|
@@ -699,9 +708,7 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
|
|
|
699
708
|
const existing = namedByPath.get(key)
|
|
700
709
|
|
|
701
710
|
if (existing && Array.isArray(existing.name)) {
|
|
702
|
-
|
|
703
|
-
for (const n of name) merged.add(n)
|
|
704
|
-
existing.name = [...merged]
|
|
711
|
+
existing.name = mergeNameArrays(existing.name, name)
|
|
705
712
|
} else {
|
|
706
713
|
const newItem: ImportNode = { ...curr, name }
|
|
707
714
|
result.push(newItem)
|
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],
|
|
@@ -268,64 +282,91 @@ export type CollectOptions<T> = CollectVisitor<T> & {
|
|
|
268
282
|
}
|
|
269
283
|
|
|
270
284
|
/**
|
|
271
|
-
*
|
|
285
|
+
* Child node fields per node kind, in traversal order (Babel's `VISITOR_KEYS`).
|
|
272
286
|
*
|
|
273
|
-
*
|
|
274
|
-
* `additionalProperties`)
|
|
275
|
-
*
|
|
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.
|
|
276
315
|
*
|
|
277
316
|
* @example
|
|
278
317
|
* ```ts
|
|
279
318
|
* const children = getChildren(operationNode, true)
|
|
280
|
-
* // returns parameters,
|
|
319
|
+
* // returns parameters, the request body, and responses
|
|
281
320
|
* ```
|
|
282
321
|
*/
|
|
283
322
|
function* getChildren(node: Node, recurse: boolean): Generator<Node, void, undefined> {
|
|
284
|
-
if (node.kind === '
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
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
|
|
297
335
|
}
|
|
298
|
-
yield* node.responses
|
|
299
|
-
|
|
300
|
-
return
|
|
301
|
-
}
|
|
302
|
-
if (node.kind === 'Schema') {
|
|
303
|
-
if (!recurse) return
|
|
304
|
-
if ('properties' in node && node.properties.length > 0) yield* node.properties
|
|
305
|
-
if ('items' in node && node.items) yield* node.items
|
|
306
|
-
if ('members' in node && node.members) yield* node.members
|
|
307
|
-
if ('additionalProperties' in node && node.additionalProperties && node.additionalProperties !== true) yield node.additionalProperties
|
|
308
|
-
|
|
309
|
-
return
|
|
310
336
|
}
|
|
311
|
-
|
|
312
|
-
yield node.schema
|
|
337
|
+
}
|
|
313
338
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
339
|
+
/**
|
|
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
|
+
}
|
|
318
353
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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).
|
|
358
|
+
*
|
|
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 })
|
|
329
370
|
}
|
|
330
371
|
|
|
331
372
|
/**
|
|
@@ -358,29 +399,7 @@ export async function walk(node: Node, options: WalkOptions): Promise<void> {
|
|
|
358
399
|
}
|
|
359
400
|
|
|
360
401
|
async function _walk(node: Node, visitor: AsyncVisitor, recurse: boolean, limit: LimitFn, parent: Node | undefined): Promise<void> {
|
|
361
|
-
|
|
362
|
-
case 'Input':
|
|
363
|
-
await limit(() => visitor.input?.(node, { parent: parent as ParentOf<InputNode> }))
|
|
364
|
-
break
|
|
365
|
-
case 'Output':
|
|
366
|
-
await limit(() => visitor.output?.(node, { parent: parent as ParentOf<OutputNode> }))
|
|
367
|
-
break
|
|
368
|
-
case 'Operation':
|
|
369
|
-
await limit(() => visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> }))
|
|
370
|
-
break
|
|
371
|
-
case 'Schema':
|
|
372
|
-
await limit(() => visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> }))
|
|
373
|
-
break
|
|
374
|
-
case 'Property':
|
|
375
|
-
await limit(() => visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> }))
|
|
376
|
-
break
|
|
377
|
-
case 'Parameter':
|
|
378
|
-
await limit(() => visitor.parameter?.(node, { parent: parent as ParentOf<ParameterNode> }))
|
|
379
|
-
break
|
|
380
|
-
case 'Response':
|
|
381
|
-
await limit(() => visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> }))
|
|
382
|
-
break
|
|
383
|
-
}
|
|
402
|
+
await limit(() => applyVisitor(node, visitor, parent))
|
|
384
403
|
|
|
385
404
|
const children = getChildren(node, recurse)
|
|
386
405
|
for (const child of children) {
|
|
@@ -424,92 +443,62 @@ export function transform(node: Node, options: TransformOptions): Node {
|
|
|
424
443
|
const { depth, parent, ...visitor } = options
|
|
425
444
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
|
|
426
445
|
|
|
427
|
-
|
|
428
|
-
|
|
446
|
+
const visited = applyVisitor<Node>(node, visitor, parent) ?? node
|
|
447
|
+
const rebuilt = transformChildren(visited, options, recurse)
|
|
429
448
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
}
|
|
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
|
|
436
453
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (node.kind === 'Operation') {
|
|
442
|
-
const op = visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> }) ?? node
|
|
443
|
-
|
|
444
|
-
return {
|
|
445
|
-
...op,
|
|
446
|
-
parameters: op.parameters.map((p) => transform(p, { ...options, parent: op })),
|
|
447
|
-
requestBody: op.requestBody
|
|
448
|
-
? {
|
|
449
|
-
...op.requestBody,
|
|
450
|
-
content: op.requestBody.content?.map((c) => ({
|
|
451
|
-
...c,
|
|
452
|
-
schema: c.schema ? transform(c.schema, { ...options, parent: op }) : undefined,
|
|
453
|
-
})),
|
|
454
|
-
}
|
|
455
|
-
: undefined,
|
|
456
|
-
responses: op.responses.map((r) => transform(r, { ...options, parent: op })),
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (node.kind === 'Schema') {
|
|
461
|
-
const schema = visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> }) ?? node
|
|
462
|
-
|
|
463
|
-
const childOptions = { ...options, parent: schema }
|
|
464
|
-
|
|
465
|
-
return {
|
|
466
|
-
...schema,
|
|
467
|
-
...('properties' in schema && recurse
|
|
468
|
-
? {
|
|
469
|
-
properties: schema.properties.map((p) => transform(p, childOptions)),
|
|
470
|
-
}
|
|
471
|
-
: {}),
|
|
472
|
-
...('items' in schema && recurse ? { items: schema.items?.map((i) => transform(i, childOptions)) } : {}),
|
|
473
|
-
...('members' in schema && recurse ? { members: schema.members?.map((m) => transform(m, childOptions)) } : {}),
|
|
474
|
-
...('additionalProperties' in schema && recurse && schema.additionalProperties && schema.additionalProperties !== true
|
|
475
|
-
? {
|
|
476
|
-
additionalProperties: transform(schema.additionalProperties, childOptions),
|
|
477
|
-
}
|
|
478
|
-
: {}),
|
|
479
|
-
} as SchemaNode
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (node.kind === 'Property') {
|
|
483
|
-
const prop = visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> }) ?? node
|
|
484
|
-
|
|
485
|
-
return createProperty({
|
|
486
|
-
...prop,
|
|
487
|
-
schema: transform(prop.schema, { ...options, parent: prop }),
|
|
488
|
-
})
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (node.kind === 'Parameter') {
|
|
492
|
-
const param = visitor.parameter?.(node, { parent: parent as ParentOf<ParameterNode> }) ?? node
|
|
493
|
-
|
|
494
|
-
return createParameter({
|
|
495
|
-
...param,
|
|
496
|
-
schema: transform(param.schema, { ...options, parent: param }),
|
|
497
|
-
})
|
|
498
|
-
}
|
|
454
|
+
const finalize = nodeFinalizers[rebuilt.kind]
|
|
455
|
+
return finalize ? finalize(rebuilt) : rebuilt
|
|
456
|
+
}
|
|
499
457
|
|
|
500
|
-
|
|
501
|
-
|
|
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
|
+
}
|
|
502
467
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
493
|
+
})
|
|
494
|
+
if (changed) (updates ??= {})[key] = mapped
|
|
495
|
+
} else if (isNode(value)) {
|
|
496
|
+
const next = transform(value, childOptions)
|
|
497
|
+
if (next !== value) (updates ??= {})[key] = next
|
|
509
498
|
}
|
|
510
499
|
}
|
|
511
500
|
|
|
512
|
-
return node
|
|
501
|
+
return updates ? ({ ...node, ...updates } as Node) : node
|
|
513
502
|
}
|
|
514
503
|
/**
|
|
515
504
|
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
@@ -531,30 +520,7 @@ export function* collectLazy<T>(node: Node, options: CollectOptions<T>): Generat
|
|
|
531
520
|
const { depth, parent, ...visitor } = options
|
|
532
521
|
const recurse = (depth ?? visitorDepths.deep) === visitorDepths.deep
|
|
533
522
|
|
|
534
|
-
|
|
535
|
-
switch (node.kind) {
|
|
536
|
-
case 'Input':
|
|
537
|
-
v = visitor.input?.(node, { parent: parent as ParentOf<InputNode> })
|
|
538
|
-
break
|
|
539
|
-
case 'Output':
|
|
540
|
-
v = visitor.output?.(node, { parent: parent as ParentOf<OutputNode> })
|
|
541
|
-
break
|
|
542
|
-
case 'Operation':
|
|
543
|
-
v = visitor.operation?.(node, { parent: parent as ParentOf<OperationNode> })
|
|
544
|
-
break
|
|
545
|
-
case 'Schema':
|
|
546
|
-
v = visitor.schema?.(node, { parent: parent as ParentOf<SchemaNode> })
|
|
547
|
-
break
|
|
548
|
-
case 'Property':
|
|
549
|
-
v = visitor.property?.(node, { parent: parent as ParentOf<PropertyNode> })
|
|
550
|
-
break
|
|
551
|
-
case 'Parameter':
|
|
552
|
-
v = visitor.parameter?.(node, { parent: parent as ParentOf<ParameterNode> })
|
|
553
|
-
break
|
|
554
|
-
case 'Response':
|
|
555
|
-
v = visitor.response?.(node, { parent: parent as ParentOf<ResponseNode> })
|
|
556
|
-
break
|
|
557
|
-
}
|
|
523
|
+
const v = applyVisitor<T>(node, visitor, parent)
|
|
558
524
|
if (v != null) yield v
|
|
559
525
|
|
|
560
526
|
for (const child of getChildren(node, recurse)) {
|