@kubb/ast 5.0.0-beta.75 → 5.0.0-beta.9
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 +68 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +48 -7
- package/dist/index.js +68 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/index.ts +1 -0
- package/src/utils.ts +89 -7
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/ast",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
4
|
-
"description": "Spec-agnostic AST layer for Kubb. Defines
|
|
3
|
+
"version": "5.0.0-beta.9",
|
|
4
|
+
"description": "Spec-agnostic AST layer for Kubb. Defines the node tree, visitor pattern, factory functions, and type guards used across all code generation plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
7
7
|
"codegen",
|
|
8
8
|
"kubb",
|
|
9
|
-
"openapi",
|
|
10
9
|
"typescript"
|
|
11
10
|
],
|
|
12
11
|
"license": "MIT",
|
|
@@ -40,7 +39,7 @@
|
|
|
40
39
|
"registry": "https://registry.npmjs.org/"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^
|
|
42
|
+
"@types/node": "^22.19.19",
|
|
44
43
|
"@internals/utils": "0.0.0"
|
|
45
44
|
},
|
|
46
45
|
"engines": {
|
package/src/index.ts
CHANGED
package/src/utils.ts
CHANGED
|
@@ -139,14 +139,14 @@ export type OperationParamsResolver = {
|
|
|
139
139
|
* @example Individual path parameter name
|
|
140
140
|
* `resolver.resolveParamName(node, param) // → 'DeletePetPathPetId'`
|
|
141
141
|
*/
|
|
142
|
-
resolveParamName(node: OperationNode, param: ParameterNode): string
|
|
142
|
+
resolveParamName(this: OperationParamsResolver, node: OperationNode, param: ParameterNode): string
|
|
143
143
|
/**
|
|
144
144
|
* Resolves the request body type name.
|
|
145
145
|
*
|
|
146
146
|
* @example Request body type name
|
|
147
147
|
* `resolver.resolveDataName(node) // → 'CreatePetData'`
|
|
148
148
|
*/
|
|
149
|
-
resolveDataName(node: OperationNode): string
|
|
149
|
+
resolveDataName(this: OperationParamsResolver, node: OperationNode): string
|
|
150
150
|
/**
|
|
151
151
|
* Resolves the grouped path parameters type name.
|
|
152
152
|
* When the return value equals `resolveParamName`, no indexed access is emitted.
|
|
@@ -154,7 +154,7 @@ export type OperationParamsResolver = {
|
|
|
154
154
|
* @example Grouped path params type name
|
|
155
155
|
* `resolver.resolvePathParamsName(node, param) // → 'DeletePetPathParams'`
|
|
156
156
|
*/
|
|
157
|
-
resolvePathParamsName(node: OperationNode, param: ParameterNode): string
|
|
157
|
+
resolvePathParamsName(this: OperationParamsResolver, node: OperationNode, param: ParameterNode): string
|
|
158
158
|
/**
|
|
159
159
|
* Resolves the grouped query parameters type name.
|
|
160
160
|
* When the return value equals `resolveParamName`, an inline struct type is emitted instead.
|
|
@@ -162,7 +162,7 @@ export type OperationParamsResolver = {
|
|
|
162
162
|
* @example Grouped query params type name
|
|
163
163
|
* `resolver.resolveQueryParamsName(node, param) // → 'FindPetsByStatusQueryParams'`
|
|
164
164
|
*/
|
|
165
|
-
resolveQueryParamsName(node: OperationNode, param: ParameterNode): string
|
|
165
|
+
resolveQueryParamsName(this: OperationParamsResolver, node: OperationNode, param: ParameterNode): string
|
|
166
166
|
/**
|
|
167
167
|
* Resolves the grouped header parameters type name.
|
|
168
168
|
* When the return value equals `resolveParamName`, an inline struct type is emitted instead.
|
|
@@ -170,7 +170,7 @@ export type OperationParamsResolver = {
|
|
|
170
170
|
* @example Grouped header params type name
|
|
171
171
|
* `resolver.resolveHeaderParamsName(node, param) // → 'DeletePetHeaderParams'`
|
|
172
172
|
*/
|
|
173
|
-
resolveHeaderParamsName(node: OperationNode, param: ParameterNode): string
|
|
173
|
+
resolveHeaderParamsName(this: OperationParamsResolver, node: OperationNode, param: ParameterNode): string
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
/**
|
|
@@ -652,6 +652,16 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
|
|
|
652
652
|
const exportedNames = new Set(exports.flatMap((e) => (Array.isArray(e.name) ? e.name : e.name ? [e.name] : [])))
|
|
653
653
|
const isUsed = (importName: string): boolean => !source || source.includes(importName) || exportedNames.has(importName)
|
|
654
654
|
|
|
655
|
+
// Memoize object import names so the same logical (propertyName, name) pair always
|
|
656
|
+
// reuses the same object reference — Set-based deduplication then works correctly.
|
|
657
|
+
const importNameMemo = new Map<string, { propertyName: string; name?: string }>()
|
|
658
|
+
const canonicalizeName = (n: string | { propertyName: string; name?: string }): string | { propertyName: string; name?: string } => {
|
|
659
|
+
if (typeof n === 'string') return n
|
|
660
|
+
const key = `${n.propertyName}:${n.name ?? ''}`
|
|
661
|
+
if (!importNameMemo.has(key)) importNameMemo.set(key, n)
|
|
662
|
+
return importNameMemo.get(key)!
|
|
663
|
+
}
|
|
664
|
+
|
|
655
665
|
const result: Array<ImportNode> = []
|
|
656
666
|
// Accumulates array-named imports keyed by `path:isTypeOnly` for name-merging
|
|
657
667
|
const namedByPath = new Map<string, ImportNode>()
|
|
@@ -669,7 +679,7 @@ export function combineImports(imports: Array<ImportNode>, exports: Array<Export
|
|
|
669
679
|
let { name } = curr
|
|
670
680
|
|
|
671
681
|
if (Array.isArray(name)) {
|
|
672
|
-
name = [...new Set(name)].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.propertyName)))
|
|
682
|
+
name = [...new Set(name.map(canonicalizeName))].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.name ?? item.propertyName)))
|
|
673
683
|
if (!name.length) continue
|
|
674
684
|
|
|
675
685
|
const key = pathTypeKey(path, isTypeOnly)
|
|
@@ -751,7 +761,19 @@ export function resolveRefName(node: SchemaNode | undefined): string | undefined
|
|
|
751
761
|
* Refs are followed by name only — the resolved `node.schema` is not traversed inline.
|
|
752
762
|
* Use this to determine schema dependencies, build reference graphs, or detect what schemas need to be emitted.
|
|
753
763
|
*
|
|
754
|
-
* @
|
|
764
|
+
* @example Collect refs from a single schema
|
|
765
|
+
* ```ts
|
|
766
|
+
* const names = collectReferencedSchemaNames(petSchema)
|
|
767
|
+
* // → Set { 'Category', 'Tag' }
|
|
768
|
+
* ```
|
|
769
|
+
*
|
|
770
|
+
* @example Accumulate refs from multiple schemas into one set
|
|
771
|
+
* ```ts
|
|
772
|
+
* const out = new Set<string>()
|
|
773
|
+
* for (const schema of schemas) {
|
|
774
|
+
* collectReferencedSchemaNames(schema, out)
|
|
775
|
+
* }
|
|
776
|
+
* ```
|
|
755
777
|
*/
|
|
756
778
|
export function collectReferencedSchemaNames(node: SchemaNode | undefined, out: Set<string> = new Set()): Set<string> {
|
|
757
779
|
if (!node) return out
|
|
@@ -768,6 +790,66 @@ export function collectReferencedSchemaNames(node: SchemaNode | undefined, out:
|
|
|
768
790
|
return out
|
|
769
791
|
}
|
|
770
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Collects the names of all top-level schemas transitively used by a set of operations.
|
|
795
|
+
*
|
|
796
|
+
* An operation uses a schema when any of its parameters, request body content, or responses
|
|
797
|
+
* reference it — directly or indirectly through other named schemas.
|
|
798
|
+
* The walk is iterative and safe against reference cycles.
|
|
799
|
+
*
|
|
800
|
+
* Use this together with `include` filters to determine which schemas from `components/schemas`
|
|
801
|
+
* are reachable from the allowed operations, so that schemas used only by excluded operations
|
|
802
|
+
* are not generated.
|
|
803
|
+
*
|
|
804
|
+
* @example Only generate schemas referenced by included operations
|
|
805
|
+
* ```ts
|
|
806
|
+
* const includedOps = inputNode.operations.filter(op => resolver.resolveOptions(op, { options, include }) !== null)
|
|
807
|
+
* const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
|
|
808
|
+
*
|
|
809
|
+
* for (const schema of inputNode.schemas) {
|
|
810
|
+
* if (schema.name && !allowed.has(schema.name)) continue
|
|
811
|
+
* // … generate schema
|
|
812
|
+
* }
|
|
813
|
+
* ```
|
|
814
|
+
*
|
|
815
|
+
* @example Check whether a specific schema is needed
|
|
816
|
+
* ```ts
|
|
817
|
+
* const allowed = collectUsedSchemaNames(includedOps, inputNode.schemas)
|
|
818
|
+
* allowed.has('OrderStatus') // false when no included operation references OrderStatus
|
|
819
|
+
* ```
|
|
820
|
+
*/
|
|
821
|
+
export function collectUsedSchemaNames(operations: ReadonlyArray<OperationNode>, schemas: ReadonlyArray<SchemaNode>): Set<string> {
|
|
822
|
+
const schemaMap = new Map<string, SchemaNode>()
|
|
823
|
+
for (const schema of schemas) {
|
|
824
|
+
if (schema.name) {
|
|
825
|
+
schemaMap.set(schema.name, schema)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const result = new Set<string>()
|
|
830
|
+
|
|
831
|
+
function visitSchema(schema: SchemaNode): void {
|
|
832
|
+
const directRefs = collectReferencedSchemaNames(schema)
|
|
833
|
+
for (const name of directRefs) {
|
|
834
|
+
if (!result.has(name)) {
|
|
835
|
+
result.add(name)
|
|
836
|
+
const namedSchema = schemaMap.get(name)
|
|
837
|
+
if (namedSchema) {
|
|
838
|
+
visitSchema(namedSchema)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
for (const op of operations) {
|
|
845
|
+
for (const schema of collect<SchemaNode>(op, { depth: 'shallow', schema: (node) => node })) {
|
|
846
|
+
visitSchema(schema)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return result
|
|
851
|
+
}
|
|
852
|
+
|
|
771
853
|
/**
|
|
772
854
|
* Identifies all schemas that participate in circular dependency chains, including direct self-loops.
|
|
773
855
|
*
|