@kubb/ast 5.0.0-beta.6 → 5.0.0-beta.60
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/LICENSE +17 -10
- package/README.md +50 -27
- package/dist/chunk-CNktS9qV.js +17 -0
- package/dist/extractStringsFromNodes-WMYJ8nQL.d.ts +82 -0
- package/dist/factory-Cl8Z7mcc.cjs +299 -0
- package/dist/factory-Cl8Z7mcc.cjs.map +1 -0
- package/dist/factory-Du7nEP4B.js +282 -0
- package/dist/factory-Du7nEP4B.js.map +1 -0
- package/dist/factory.cjs +29 -0
- package/dist/factory.d.ts +62 -0
- package/dist/factory.js +3 -0
- package/dist/index-BzjwdK2M.d.ts +2433 -0
- package/dist/index.cjs +442 -2180
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +93 -3408
- package/dist/index.js +392 -2101
- package/dist/index.js.map +1 -1
- package/dist/operationParams-BZ07xDm0.d.ts +204 -0
- package/dist/response-DKxTr522.js +683 -0
- package/dist/response-DKxTr522.js.map +1 -0
- package/dist/response-DS5S3IG4.cjs +1058 -0
- package/dist/response-DS5S3IG4.cjs.map +1 -0
- package/dist/types-olVl9v5p.d.ts +764 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/dist/utils-D83JA6Xx.cjs +1645 -0
- package/dist/utils-D83JA6Xx.cjs.map +1 -0
- package/dist/utils-Dj_KoXMv.js +1389 -0
- package/dist/utils-Dj_KoXMv.js.map +1 -0
- package/dist/utils.cjs +34 -0
- package/dist/utils.d.ts +332 -0
- package/dist/utils.js +3 -0
- package/package.json +17 -6
- package/src/constants.ts +19 -64
- package/src/dedupe.ts +239 -0
- package/src/dialect.ts +53 -0
- package/src/factory.ts +67 -678
- package/src/guards.ts +10 -92
- package/src/index.ts +16 -43
- package/src/infer.ts +16 -14
- package/src/mocks.ts +7 -127
- package/src/node.ts +128 -0
- package/src/nodes/base.ts +5 -12
- package/src/nodes/code.ts +165 -74
- package/src/nodes/content.ts +56 -0
- package/src/nodes/file.ts +97 -36
- package/src/nodes/function.ts +216 -156
- package/src/nodes/http.ts +1 -35
- package/src/nodes/index.ts +23 -15
- package/src/nodes/input.ts +140 -0
- package/src/nodes/operation.ts +122 -68
- package/src/nodes/output.ts +23 -0
- package/src/nodes/parameter.ts +33 -3
- package/src/nodes/property.ts +36 -3
- package/src/nodes/requestBody.ts +61 -0
- package/src/nodes/response.ts +58 -13
- package/src/nodes/schema.ts +93 -17
- package/src/printer.ts +48 -42
- package/src/registry.ts +75 -0
- package/src/signature.ts +207 -0
- package/src/transformers.ts +50 -18
- package/src/types.ts +7 -68
- package/src/utils/codegen.ts +104 -0
- package/src/utils/extractStringsFromNodes.ts +34 -0
- package/src/utils/fileMerge.ts +184 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/operationParams.ts +353 -0
- package/src/utils/refs.ts +112 -0
- package/src/utils/schemaGraph.ts +169 -0
- package/src/utils/schemaTraversal.ts +86 -0
- package/src/utils/strings.ts +139 -0
- package/src/visitor.ts +227 -289
- package/dist/chunk--u3MIqq1.js +0 -8
- package/src/nodes/root.ts +0 -64
- package/src/refs.ts +0 -67
- package/src/resolvers.ts +0 -45
- package/src/utils.ts +0 -915
package/src/transformers.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isScalarPrimitive } from './constants.ts'
|
|
2
|
-
import { createProperty, createSchema } from './factory.ts'
|
|
3
2
|
import { narrowSchema } from './guards.ts'
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import { createProperty } from './nodes/property.ts'
|
|
4
|
+
import { createSchema, type SchemaNode } from './nodes/schema.ts'
|
|
5
|
+
import { enumPropName } from './utils/refs.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Replaces a discriminator property's schema with a string enum of allowed values.
|
|
@@ -73,25 +73,30 @@ export function setDiscriminatorEnum({
|
|
|
73
73
|
* ])
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
|
-
export function
|
|
77
|
-
|
|
76
|
+
export function* mergeAdjacentObjectsLazy(members: Iterable<SchemaNode>): Generator<SchemaNode, void, undefined> {
|
|
77
|
+
let acc: SchemaNode | undefined
|
|
78
|
+
|
|
79
|
+
for (const member of members) {
|
|
78
80
|
const objectMember = narrowSchema(member, 'object')
|
|
79
|
-
if (objectMember && !objectMember.name) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
...previousObject,
|
|
86
|
-
properties: [...(previousObject.properties ?? []), ...(objectMember.properties ?? [])],
|
|
81
|
+
if (objectMember && !objectMember.name && acc !== undefined) {
|
|
82
|
+
const accObject = narrowSchema(acc, 'object')
|
|
83
|
+
if (accObject && !accObject.name) {
|
|
84
|
+
acc = createSchema({
|
|
85
|
+
...accObject,
|
|
86
|
+
properties: [...(accObject.properties ?? []), ...(objectMember.properties ?? [])],
|
|
87
87
|
})
|
|
88
|
-
|
|
88
|
+
continue
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
if (acc !== undefined) yield acc
|
|
92
|
+
acc = member
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (acc !== undefined) yield acc
|
|
96
|
+
}
|
|
91
97
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}, [])
|
|
98
|
+
export function mergeAdjacentObjects(members: Array<SchemaNode>): Array<SchemaNode> {
|
|
99
|
+
return [...mergeAdjacentObjectsLazy(members)]
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
/**
|
|
@@ -145,7 +150,7 @@ export function setEnumName(propNode: SchemaNode, parentName: string | null | un
|
|
|
145
150
|
const enumNode = narrowSchema(propNode, 'enum')
|
|
146
151
|
|
|
147
152
|
if (enumNode?.primitive === 'boolean') {
|
|
148
|
-
return { ...propNode, name:
|
|
153
|
+
return { ...propNode, name: null }
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
if (enumNode) {
|
|
@@ -157,3 +162,30 @@ export function setEnumName(propNode: SchemaNode, parentName: string | null | un
|
|
|
157
162
|
|
|
158
163
|
return propNode
|
|
159
164
|
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merges a ref node with its resolved schema, giving usage-site fields precedence.
|
|
168
|
+
*
|
|
169
|
+
* Usage-site fields (`description`, `readOnly`, `nullable`, `deprecated`) on the ref node override
|
|
170
|
+
* the same fields in the resolved `node.schema`. Non-ref nodes are returned unchanged.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* // Ref with description override
|
|
175
|
+
* const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A cute pet' })
|
|
176
|
+
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function syncSchemaRef(node: SchemaNode): SchemaNode {
|
|
180
|
+
const ref = narrowSchema(node, 'ref')
|
|
181
|
+
|
|
182
|
+
if (!ref) return node
|
|
183
|
+
if (!ref.schema) return node
|
|
184
|
+
|
|
185
|
+
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref
|
|
186
|
+
|
|
187
|
+
// Filter out undefined override values so they don't shadow the resolved schema's fields.
|
|
188
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined))
|
|
189
|
+
|
|
190
|
+
return createSchema({ ...ref.schema, ...definedOverrides })
|
|
191
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
export type {
|
|
3
|
-
export type {
|
|
4
|
-
export type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
BaseNode,
|
|
8
|
-
BreakNode,
|
|
9
|
-
CodeNode,
|
|
10
|
-
ComplexSchemaType,
|
|
11
|
-
ConstNode,
|
|
12
|
-
DateSchemaNode,
|
|
13
|
-
DatetimeSchemaNode,
|
|
14
|
-
EnumSchemaNode,
|
|
15
|
-
EnumValueNode,
|
|
16
|
-
ExportNode,
|
|
17
|
-
FileNode,
|
|
18
|
-
FormatStringSchemaNode,
|
|
19
|
-
FunctionNode,
|
|
20
|
-
FunctionNodeType,
|
|
21
|
-
FunctionParameterNode,
|
|
22
|
-
FunctionParametersNode,
|
|
23
|
-
FunctionParamNode,
|
|
24
|
-
HttpMethod,
|
|
25
|
-
HttpStatusCode,
|
|
26
|
-
ImportNode,
|
|
27
|
-
InputMeta,
|
|
28
|
-
InputNode,
|
|
29
|
-
IntersectionSchemaNode,
|
|
30
|
-
Ipv4SchemaNode,
|
|
31
|
-
Ipv6SchemaNode,
|
|
32
|
-
JSDocNode,
|
|
33
|
-
JsxNode,
|
|
34
|
-
MediaType,
|
|
35
|
-
Node,
|
|
36
|
-
NodeKind,
|
|
37
|
-
NumberSchemaNode,
|
|
38
|
-
ObjectSchemaNode,
|
|
39
|
-
OperationNode,
|
|
40
|
-
OutputNode,
|
|
41
|
-
ParameterGroupNode,
|
|
42
|
-
ParameterLocation,
|
|
43
|
-
ParameterNode,
|
|
44
|
-
ParamsTypeNode,
|
|
45
|
-
PrimitiveSchemaType,
|
|
46
|
-
PropertyNode,
|
|
47
|
-
RefSchemaNode,
|
|
48
|
-
ResponseNode,
|
|
49
|
-
ScalarSchemaNode,
|
|
50
|
-
ScalarSchemaType,
|
|
51
|
-
SchemaNode,
|
|
52
|
-
SchemaNodeByType,
|
|
53
|
-
SchemaType,
|
|
54
|
-
SourceNode,
|
|
55
|
-
SpecialSchemaType,
|
|
56
|
-
StatusCode,
|
|
57
|
-
StringSchemaNode,
|
|
58
|
-
TextNode,
|
|
59
|
-
TimeSchemaNode,
|
|
60
|
-
TypeDeclarationNode,
|
|
61
|
-
TypeNode,
|
|
62
|
-
UnionSchemaNode,
|
|
63
|
-
UrlSchemaNode,
|
|
64
|
-
} from './nodes/index.ts'
|
|
65
|
-
export type { RefMap } from './refs.ts'
|
|
66
|
-
export type { AsyncVisitor, CollectOptions, CollectVisitor, ParentOf, TransformOptions, Visitor, VisitorContext, WalkOptions } from './visitor.ts'
|
|
1
|
+
export type { DedupeCanonical, DedupeLookups, DedupePlan } from './dedupe.ts'
|
|
2
|
+
export type { SchemaDialect } from './dialect.ts'
|
|
3
|
+
export type { DistributiveOmit } from './node.ts'
|
|
4
|
+
export type { InferSchemaNode, ParserOptions } from './infer.ts'
|
|
5
|
+
export type * from './nodes/index.ts'
|
|
6
|
+
export type { ParentOf, Visitor, VisitorContext } from './visitor.ts'
|
|
67
7
|
export type { Printer, PrinterFactoryOptions, PrinterPartial } from './printer.ts'
|
|
68
|
-
export type {
|
|
69
|
-
export type { OperationParamsResolver } from './utils.ts'
|
|
8
|
+
export type { OperationParamsResolver } from './utils/operationParams.ts'
|
|
70
9
|
export type { UserFileNode } from './factory.ts'
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isIdentifier, singleQuote } from '@internals/utils'
|
|
2
|
+
import { INDENT } from '../constants.ts'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds a JSDoc comment block from an array of lines, returning `fallback` when there are no
|
|
6
|
+
* comments so callers always get a usable string.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* buildJSDoc(['@type string', '@example hello'])
|
|
11
|
+
* // '/**\n * @type string\n * @example hello\n *\/\n '
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function buildJSDoc(
|
|
15
|
+
comments: Array<string>,
|
|
16
|
+
options: {
|
|
17
|
+
/**
|
|
18
|
+
* String used to indent each comment line.
|
|
19
|
+
* @default ' * '
|
|
20
|
+
*/
|
|
21
|
+
indent?: string
|
|
22
|
+
/**
|
|
23
|
+
* String appended after the closing tag.
|
|
24
|
+
* @default '\n '
|
|
25
|
+
*/
|
|
26
|
+
suffix?: string
|
|
27
|
+
/**
|
|
28
|
+
* Returned as-is when `comments` is empty.
|
|
29
|
+
* @default ' '
|
|
30
|
+
*/
|
|
31
|
+
fallback?: string
|
|
32
|
+
} = {},
|
|
33
|
+
): string {
|
|
34
|
+
const { indent = ' * ', suffix = '\n ', fallback = ' ' } = options
|
|
35
|
+
|
|
36
|
+
if (comments.length === 0) return fallback
|
|
37
|
+
|
|
38
|
+
return `/**\n${comments.map((c) => `${indent}${c}`).join('\n')}\n */${suffix}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
|
|
43
|
+
*/
|
|
44
|
+
function indentLines(text: string): string {
|
|
45
|
+
if (!text) return ''
|
|
46
|
+
return text
|
|
47
|
+
.split('\n')
|
|
48
|
+
.map((line) => (line.trim() ? `${INDENT}${line}` : ''))
|
|
49
|
+
.join('\n')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Renders an object key, quoting it with single quotes only when it is not a valid identifier.
|
|
54
|
+
* Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* objectKey('name') // 'name'
|
|
59
|
+
* objectKey('x-total') // "'x-total'"
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function objectKey(name: string): string {
|
|
63
|
+
return isIdentifier(name) ? name : singleQuote(name)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
|
|
68
|
+
* level and closing the brace at column zero. Nested objects built the same way indent cumulatively,
|
|
69
|
+
* so callers never re-parse the generated code. A trailing comma is added per entry to match the
|
|
70
|
+
* formatter's multi-line style.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* buildObject(['id: z.number()', 'name: z.string()'])
|
|
75
|
+
* // '{\n id: z.number(),\n name: z.string(),\n}'
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export function buildObject(entries: Array<string>): string {
|
|
79
|
+
if (entries.length === 0) return '{}'
|
|
80
|
+
const body = entries.map((entry) => `${indentLines(entry)},`).join('\n')
|
|
81
|
+
|
|
82
|
+
return `{\n${body}\n}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
|
|
87
|
+
* one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
|
|
88
|
+
* one level with a trailing comma and the closing bracket at column zero. Use it for `z.union([…])`,
|
|
89
|
+
* `z.array([…])`, and similar member lists so objects inside them nest correctly.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* buildList(['z.string()', 'z.number()'])
|
|
94
|
+
* // '[z.string(), z.number()]'
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function buildList(items: Array<string>, brackets: [open: string, close: string] = ['[', ']']): string {
|
|
98
|
+
const [open, close] = brackets
|
|
99
|
+
if (items.length === 0) return `${open}${close}`
|
|
100
|
+
if (!items.some((item) => item.includes('\n'))) return `${open}${items.join(', ')}${close}`
|
|
101
|
+
const body = items.map((item) => `${indentLines(item)},`).join('\n')
|
|
102
|
+
|
|
103
|
+
return `${open}\n${body}\n${close}`
|
|
104
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CodeNode } from '../nodes/code.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts all string content from a `CodeNode` tree recursively.
|
|
5
|
+
*
|
|
6
|
+
* Collects text node values, identifier references in string fields (`params`, `generics`, `returnType`, `type`),
|
|
7
|
+
* and nested node content. Used to build the full source string for import filtering.
|
|
8
|
+
*/
|
|
9
|
+
export function extractStringsFromNodes(nodes: Array<CodeNode> | undefined): string {
|
|
10
|
+
if (!nodes?.length) return ''
|
|
11
|
+
return nodes
|
|
12
|
+
.map((node) => {
|
|
13
|
+
// Backward-compat: compiled plugins may still pass bare strings at runtime
|
|
14
|
+
if (typeof node === 'string') return node as string
|
|
15
|
+
if (node.kind === 'Text') return node.value
|
|
16
|
+
if (node.kind === 'Break') return ''
|
|
17
|
+
if (node.kind === 'Jsx') return node.value
|
|
18
|
+
|
|
19
|
+
const parts: Array<string> = []
|
|
20
|
+
|
|
21
|
+
if ('params' in node && node.params) parts.push(node.params)
|
|
22
|
+
if ('generics' in node && node.generics) parts.push(Array.isArray(node.generics) ? node.generics.join(', ') : node.generics)
|
|
23
|
+
if ('returnType' in node && node.returnType) parts.push(node.returnType)
|
|
24
|
+
if ('type' in node && typeof node.type === 'string') parts.push(node.type)
|
|
25
|
+
|
|
26
|
+
const nested = extractStringsFromNodes(node.nodes)
|
|
27
|
+
|
|
28
|
+
if (nested) parts.push(nested)
|
|
29
|
+
|
|
30
|
+
return parts.join('\n')
|
|
31
|
+
})
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.join('\n')
|
|
34
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-member merging. `combineImports`, `combineExports`, and `combineSources` deduplicate and sort
|
|
3
|
+
* the import, export, and source entries of one file, and drop imports nothing references. This works
|
|
4
|
+
* on a file's members, not on schema content.
|
|
5
|
+
*
|
|
6
|
+
* For collapsing duplicate schema shapes by structural signature, see `dedupe.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExportNode, ImportNode, SourceNode } from '../nodes/index.ts'
|
|
9
|
+
import { extractStringsFromNodes } from './extractStringsFromNodes.ts'
|
|
10
|
+
|
|
11
|
+
function sourceKey(source: SourceNode): string {
|
|
12
|
+
const nameKey = source.name ?? extractStringsFromNodes(source.nodes)
|
|
13
|
+
return `${nameKey}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pathTypeKey(path: string, isTypeOnly: boolean | null | undefined): string {
|
|
17
|
+
return `${path}:${isTypeOnly ?? false}`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function exportKey(path: string, name: string | null | undefined, isTypeOnly: boolean | null | undefined, asAlias: boolean | null | undefined): string {
|
|
21
|
+
return `${path}:${name ?? ''}:${isTypeOnly ?? false}:${asAlias ?? ''}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function importKey(path: string, name: string | null | undefined, isTypeOnly: boolean | null | undefined): string {
|
|
25
|
+
return `${path}:${name ?? ''}:${isTypeOnly ?? false}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Computes a multi-level sort key for exports and imports:
|
|
30
|
+
* non-array names first (wildcards/namespace aliases). Type-only before value. Alphabetical path. Unnamed before named.
|
|
31
|
+
*/
|
|
32
|
+
function sortKey(node: { name?: string | Array<unknown> | null; isTypeOnly?: boolean | null; path: string }): string {
|
|
33
|
+
const isArray = Array.isArray(node.name) ? '1' : '0'
|
|
34
|
+
const typeOnly = node.isTypeOnly ? '0' : '1'
|
|
35
|
+
const hasName = node.name != null ? '1' : '0'
|
|
36
|
+
const name = Array.isArray(node.name) ? node.name.toSorted().join('\0') : (node.name ?? '')
|
|
37
|
+
return `${isArray}:${typeOnly}:${node.path}:${hasName}:${name}`
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Deduplicates and merges `SourceNode` objects by `name + isExportable + isTypeOnly`.
|
|
42
|
+
*
|
|
43
|
+
* Unnamed sources are deduplicated by object reference. Returns a deduplicated array in original order.
|
|
44
|
+
*/
|
|
45
|
+
export function combineSources(sources: Array<SourceNode>): Array<SourceNode> {
|
|
46
|
+
const seen = new Map<string, SourceNode>()
|
|
47
|
+
for (const source of sources) {
|
|
48
|
+
const key = sourceKey(source)
|
|
49
|
+
if (!seen.has(key)) seen.set(key, source)
|
|
50
|
+
}
|
|
51
|
+
return [...seen.values()]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merges `incoming` names into `existing`, preserving order and dropping duplicates.
|
|
56
|
+
*
|
|
57
|
+
* Shared by `combineExports` and `combineImports` for the same-path name-merge case.
|
|
58
|
+
*/
|
|
59
|
+
function mergeNameArrays<TName>(existing: Array<TName>, incoming: Array<TName>): Array<TName> {
|
|
60
|
+
const merged = new Set(existing)
|
|
61
|
+
for (const name of incoming) merged.add(name)
|
|
62
|
+
return [...merged]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Deduplicates and merges `ExportNode` objects by path and type.
|
|
67
|
+
*
|
|
68
|
+
* Named exports with the same path and `isTypeOnly` flag have their names merged into a single export.
|
|
69
|
+
* Non-array exports are deduplicated by exact identity. Returns a sorted, deduplicated array.
|
|
70
|
+
*/
|
|
71
|
+
export function combineExports(exports: Array<ExportNode>): Array<ExportNode> {
|
|
72
|
+
const result: Array<ExportNode> = []
|
|
73
|
+
// Accumulates array-named exports keyed by `path:isTypeOnly` for name-merging
|
|
74
|
+
const namedByPath = new Map<string, ExportNode>()
|
|
75
|
+
// Deduplicates non-array exports by their exact identity
|
|
76
|
+
const seen = new Set<string>()
|
|
77
|
+
|
|
78
|
+
// Precompute sort keys once, avoids recomputing per comparison.
|
|
79
|
+
const keyed = exports.map((node) => ({ node, key: sortKey(node) }))
|
|
80
|
+
keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
81
|
+
|
|
82
|
+
for (const { node: curr } of keyed) {
|
|
83
|
+
const { name, path, isTypeOnly, asAlias } = curr
|
|
84
|
+
|
|
85
|
+
if (Array.isArray(name)) {
|
|
86
|
+
if (!name.length) continue
|
|
87
|
+
|
|
88
|
+
const key = pathTypeKey(path, isTypeOnly)
|
|
89
|
+
const existing = namedByPath.get(key)
|
|
90
|
+
|
|
91
|
+
if (existing && Array.isArray(existing.name)) {
|
|
92
|
+
existing.name = mergeNameArrays(existing.name, name)
|
|
93
|
+
} else {
|
|
94
|
+
const newItem: ExportNode = { ...curr, name: [...new Set(name)] }
|
|
95
|
+
result.push(newItem)
|
|
96
|
+
namedByPath.set(key, newItem)
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
const key = exportKey(path, name, isTypeOnly, asAlias)
|
|
100
|
+
if (!seen.has(key)) {
|
|
101
|
+
result.push(curr)
|
|
102
|
+
seen.add(key)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Deduplicates and merges `ImportNode` objects, filtering out unused imports.
|
|
112
|
+
*
|
|
113
|
+
* Retains imports that are referenced in `source` or re-exported. Imports with the same path and
|
|
114
|
+
* `isTypeOnly` flag have their names merged. Returns a sorted, deduplicated, filtered array.
|
|
115
|
+
*/
|
|
116
|
+
export function combineImports(imports: Array<ImportNode>, exports: Array<ExportNode>, source?: string): Array<ImportNode> {
|
|
117
|
+
// Build a lookup of all exported names to retain imports that are re-exported
|
|
118
|
+
const exportedNames = new Set(exports.flatMap((e) => (Array.isArray(e.name) ? e.name : e.name ? [e.name] : [])))
|
|
119
|
+
const isUsed = (importName: string): boolean => !source || source.includes(importName) || exportedNames.has(importName)
|
|
120
|
+
|
|
121
|
+
// Memoize object import names so the same logical (propertyName, name) pair always
|
|
122
|
+
// reuses the same object reference. Set-based deduplication then works correctly.
|
|
123
|
+
const importNameMemo = new Map<string, { propertyName: string; name?: string }>()
|
|
124
|
+
const canonicalizeName = (n: string | { propertyName: string; name?: string }): string | { propertyName: string; name?: string } => {
|
|
125
|
+
if (typeof n === 'string') return n
|
|
126
|
+
const key = `${n.propertyName}:${n.name ?? ''}`
|
|
127
|
+
if (!importNameMemo.has(key)) importNameMemo.set(key, n)
|
|
128
|
+
return importNameMemo.get(key)!
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Paths that keep at least one used named import. A default import from such a path is retained
|
|
132
|
+
// even when its binding can't be found in `source` e.g. a generated `client` default import
|
|
133
|
+
// alongside `import type { Client } from <same path>`, where merged grouped output omits the body.
|
|
134
|
+
const pathsWithUsedNamedImport = new Set<string>()
|
|
135
|
+
for (const node of imports) {
|
|
136
|
+
if (!Array.isArray(node.name)) continue
|
|
137
|
+
if (node.name.some((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.name ?? item.propertyName)))) {
|
|
138
|
+
pathsWithUsedNamedImport.add(node.path)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const result: Array<ImportNode> = []
|
|
143
|
+
// Accumulates array-named imports keyed by `path:isTypeOnly` for name-merging
|
|
144
|
+
const namedByPath = new Map<string, ImportNode>()
|
|
145
|
+
// Deduplicates non-array imports by their exact identity
|
|
146
|
+
const seen = new Set<string>()
|
|
147
|
+
|
|
148
|
+
// Precompute sort keys once, avoids recomputing per comparison.
|
|
149
|
+
const keyed = imports.map((node) => ({ node, key: sortKey(node) }))
|
|
150
|
+
keyed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0))
|
|
151
|
+
|
|
152
|
+
for (const { node: curr } of keyed) {
|
|
153
|
+
if (curr.path === curr.root) continue
|
|
154
|
+
|
|
155
|
+
const { path, isTypeOnly } = curr
|
|
156
|
+
let { name } = curr
|
|
157
|
+
|
|
158
|
+
if (Array.isArray(name)) {
|
|
159
|
+
name = [...new Set(name.map(canonicalizeName))].filter((item) => (typeof item === 'string' ? isUsed(item) : isUsed(item.name ?? item.propertyName)))
|
|
160
|
+
if (!name.length) continue
|
|
161
|
+
|
|
162
|
+
const key = pathTypeKey(path, isTypeOnly)
|
|
163
|
+
const existing = namedByPath.get(key)
|
|
164
|
+
|
|
165
|
+
if (existing && Array.isArray(existing.name)) {
|
|
166
|
+
existing.name = mergeNameArrays(existing.name, name)
|
|
167
|
+
} else {
|
|
168
|
+
const newItem: ImportNode = { ...curr, name }
|
|
169
|
+
result.push(newItem)
|
|
170
|
+
namedByPath.set(key, newItem)
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
if (name && !isUsed(name) && !pathsWithUsedNamedImport.has(path)) continue
|
|
174
|
+
|
|
175
|
+
const key = importKey(path, name, isTypeOnly)
|
|
176
|
+
if (!seen.has(key)) {
|
|
177
|
+
result.push(curr)
|
|
178
|
+
seen.add(key)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result
|
|
184
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { isValidVarName } from '@internals/utils'
|
|
2
|
+
export { buildJSDoc, buildList, buildObject, objectKey } from './codegen.ts'
|
|
3
|
+
export { extractStringsFromNodes } from './extractStringsFromNodes.ts'
|
|
4
|
+
export { childName, enumPropName, extractRefName, isStringType, resolveGroupType, resolveRefName } from './refs.ts'
|
|
5
|
+
export { syncSchemaRef } from '../transformers.ts'
|
|
6
|
+
export { getNestedAccessor, jsStringEscape, stringify, stringifyObject, toRegExpString, trimQuotes } from './strings.ts'
|
|
7
|
+
export { collectUsedSchemaNames, containsCircularRef, findCircularSchemas } from './schemaGraph.ts'
|
|
8
|
+
export { lazyGetter, mapSchemaItems, mapSchemaMembers, mapSchemaProperties } from './schemaTraversal.ts'
|
|
9
|
+
export type { MappedProperty, MappedSchema, SchemaTransform } from './schemaTraversal.ts'
|
|
10
|
+
export { buildGroupParam, buildTypeLiteral, caseParams, createOperationParams, resolveParamType } from './operationParams.ts'
|
|
11
|
+
export type { BuildGroupArgs, ParamGroupType } from './operationParams.ts'
|