@kubb/ast 5.0.0-alpha.66 → 5.0.0-alpha.69
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 +776 -664
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +72 -1
- package/dist/index.js +773 -665
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +12 -1
- package/src/utils.ts +132 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -34,5 +34,16 @@ export { childName, collectImports, enumPropName, findDiscriminator } from './re
|
|
|
34
34
|
export { mergeAdjacentObjects, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
|
|
35
35
|
export type * from './types.ts'
|
|
36
36
|
export type { OperationParamsResolver } from './utils.ts'
|
|
37
|
-
export {
|
|
37
|
+
export {
|
|
38
|
+
caseParams,
|
|
39
|
+
collectReferencedSchemaNames,
|
|
40
|
+
containsCircularRef,
|
|
41
|
+
createDiscriminantNode,
|
|
42
|
+
createOperationParams,
|
|
43
|
+
extractStringsFromNodes,
|
|
44
|
+
findCircularSchemas,
|
|
45
|
+
isStringType,
|
|
46
|
+
resolveRefName,
|
|
47
|
+
syncSchemaRef,
|
|
48
|
+
} from './utils.ts'
|
|
38
49
|
export { collect, transform, walk } from './visitor.ts'
|
package/src/utils.ts
CHANGED
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
SourceNode,
|
|
17
17
|
} from './nodes/index.ts'
|
|
18
18
|
import type { SchemaType } from './nodes/schema.ts'
|
|
19
|
+
import { extractRefName } from './refs.ts'
|
|
20
|
+
import { collect } from './visitor.ts'
|
|
19
21
|
|
|
20
22
|
const plainStringTypes = new Set<SchemaType>(['string', 'uuid', 'email', 'url', 'datetime'] as const)
|
|
21
23
|
|
|
@@ -739,3 +741,133 @@ export function extractStringsFromNodes(nodes: Array<CodeNode> | undefined): str
|
|
|
739
741
|
.filter(Boolean)
|
|
740
742
|
.join('\n')
|
|
741
743
|
}
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* Resolves the referenced schema name of a `ref` node, falling back through
|
|
747
|
+
* `ref` → `name` → nested `schema.name`. Returns `undefined` for non-ref
|
|
748
|
+
* nodes or when no name can be resolved.
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```ts
|
|
752
|
+
* resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' })
|
|
753
|
+
* // => 'Pet'
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
export function resolveRefName(node: SchemaNode | undefined): string | undefined {
|
|
757
|
+
if (!node || node.type !== 'ref') return undefined
|
|
758
|
+
if (node.ref) return extractRefName(node.ref) ?? node.name ?? node.schema?.name ?? undefined
|
|
759
|
+
|
|
760
|
+
return node.name ?? node.schema?.name ?? undefined
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Recursively collects every named schema referenced (transitively) from
|
|
765
|
+
* `node` via `ref` edges. Refs are followed by name only — the resolved
|
|
766
|
+
* `node.schema` of a ref is not traversed inline.
|
|
767
|
+
*
|
|
768
|
+
* @example
|
|
769
|
+
* ```ts
|
|
770
|
+
* const refs = collectReferencedSchemaNames(petSchema)
|
|
771
|
+
* // => Set { 'Cat', 'Dog' }
|
|
772
|
+
* ```
|
|
773
|
+
*/
|
|
774
|
+
export function collectReferencedSchemaNames(node: SchemaNode | undefined, out: Set<string> = new Set()): Set<string> {
|
|
775
|
+
if (!node) return out
|
|
776
|
+
collect<void>(node, {
|
|
777
|
+
schema(child) {
|
|
778
|
+
if (child.type === 'ref') {
|
|
779
|
+
const name = resolveRefName(child)
|
|
780
|
+
|
|
781
|
+
if (name) out.add(name)
|
|
782
|
+
}
|
|
783
|
+
return undefined
|
|
784
|
+
},
|
|
785
|
+
})
|
|
786
|
+
return out
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Identifies every named schema that participates in a circular dependency
|
|
791
|
+
* chain — including direct self-loops (e.g. `TreeNode → TreeNode`) and indirect
|
|
792
|
+
* cycles spanning multiple schemas (e.g. `Pet → Cat → Pet`).
|
|
793
|
+
*
|
|
794
|
+
* The returned set contains schema names. Plugins that translate schemas into
|
|
795
|
+
* a host language can use this to wrap recursive positions in a deferred
|
|
796
|
+
* construct (lazy getter, `z.lazy(() => …)`, etc.) and avoid runtime stack
|
|
797
|
+
* overflows when the generated code is executed.
|
|
798
|
+
*
|
|
799
|
+
* Refs are followed by name only — `node.schema` (the resolved referent) is
|
|
800
|
+
* not traversed inline, which keeps the algorithm linear in the size of the
|
|
801
|
+
* schema graph.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* const circular = findCircularSchemas(inputNode.schemas)
|
|
806
|
+
* if (circular.has('Pet')) {
|
|
807
|
+
* // emit lazy wrapper for any property whose schema references Pet
|
|
808
|
+
* }
|
|
809
|
+
* ```
|
|
810
|
+
*/
|
|
811
|
+
export function findCircularSchemas(schemas: ReadonlyArray<SchemaNode>): Set<string> {
|
|
812
|
+
const graph = new Map<string, Set<string>>()
|
|
813
|
+
|
|
814
|
+
for (const schema of schemas) {
|
|
815
|
+
if (!schema.name) continue
|
|
816
|
+
graph.set(schema.name, collectReferencedSchemaNames(schema))
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const circular = new Set<string>()
|
|
820
|
+
for (const start of graph.keys()) {
|
|
821
|
+
const visited = new Set<string>()
|
|
822
|
+
const stack: string[] = [...(graph.get(start) ?? [])]
|
|
823
|
+
while (stack.length > 0) {
|
|
824
|
+
const node = stack.pop()!
|
|
825
|
+
if (node === start) {
|
|
826
|
+
circular.add(start)
|
|
827
|
+
break
|
|
828
|
+
}
|
|
829
|
+
if (visited.has(node)) continue
|
|
830
|
+
visited.add(node)
|
|
831
|
+
|
|
832
|
+
const next = graph.get(node)
|
|
833
|
+
if (next) for (const r of next) stack.push(r)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return circular
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Returns true when `node` (or anything nested within it) carries a `ref`
|
|
842
|
+
* whose resolved name belongs to `circularSchemas`.
|
|
843
|
+
*
|
|
844
|
+
* When `excludeName` is provided, refs to that name are ignored — useful
|
|
845
|
+
* when self-references are already handled separately from cross-schema
|
|
846
|
+
* cycles (e.g. the faker plugin emits `undefined as any` for direct
|
|
847
|
+
* self-recursion but a lazy getter for indirect cycles).
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```ts
|
|
851
|
+
* const circular = findCircularSchemas(schemas)
|
|
852
|
+
* if (containsCircularRef(property.schema, { circularSchemas: circular, excludeName: 'Pet' })) {
|
|
853
|
+
* // emit `get foo() { return fakeCat() }` instead of eager call
|
|
854
|
+
* }
|
|
855
|
+
* ```
|
|
856
|
+
*/
|
|
857
|
+
export function containsCircularRef(
|
|
858
|
+
node: SchemaNode | undefined,
|
|
859
|
+
{ circularSchemas, excludeName }: { circularSchemas: ReadonlySet<string>; excludeName?: string },
|
|
860
|
+
): boolean {
|
|
861
|
+
if (!node || circularSchemas.size === 0) return false
|
|
862
|
+
|
|
863
|
+
const matches = collect<true>(node, {
|
|
864
|
+
schema(child) {
|
|
865
|
+
if (child.type !== 'ref') return undefined
|
|
866
|
+
const name = resolveRefName(child)
|
|
867
|
+
|
|
868
|
+
return name && name !== excludeName && circularSchemas.has(name) ? true : undefined
|
|
869
|
+
},
|
|
870
|
+
})
|
|
871
|
+
|
|
872
|
+
return matches.length > 0
|
|
873
|
+
}
|