@kubb/plugin-faker 5.0.0-alpha.9 → 5.0.0-beta.4

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.
Files changed (44) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -4
  3. package/dist/Faker-BgleOzVN.cjs +486 -0
  4. package/dist/Faker-BgleOzVN.cjs.map +1 -0
  5. package/dist/Faker-CdyPfOPg.d.ts +27 -0
  6. package/dist/Faker-fcQEB9i5.js +384 -0
  7. package/dist/Faker-fcQEB9i5.js.map +1 -0
  8. package/dist/components.cjs +2 -2
  9. package/dist/components.d.ts +2 -31
  10. package/dist/components.js +1 -1
  11. package/dist/fakerGenerator-C3Ho3BaI.d.ts +9 -0
  12. package/dist/fakerGenerator-D7daHCh6.js +516 -0
  13. package/dist/fakerGenerator-D7daHCh6.js.map +1 -0
  14. package/dist/fakerGenerator-VJEVzLjc.cjs +526 -0
  15. package/dist/fakerGenerator-VJEVzLjc.cjs.map +1 -0
  16. package/dist/generators.cjs +1 -1
  17. package/dist/generators.d.ts +2 -505
  18. package/dist/generators.js +1 -1
  19. package/dist/index.cjs +136 -84
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.d.ts +28 -4
  22. package/dist/index.js +128 -83
  23. package/dist/index.js.map +1 -1
  24. package/dist/printerFaker-CJiwzoto.d.ts +206 -0
  25. package/extension.yaml +364 -0
  26. package/package.json +52 -50
  27. package/src/components/Faker.tsx +124 -78
  28. package/src/generators/fakerGenerator.tsx +235 -134
  29. package/src/index.ts +7 -2
  30. package/src/plugin.ts +60 -121
  31. package/src/printers/printerFaker.ts +341 -0
  32. package/src/resolvers/resolverFaker.ts +92 -0
  33. package/src/types.ts +127 -81
  34. package/src/utils.ts +356 -0
  35. package/dist/components-BkBIov4R.js +0 -419
  36. package/dist/components-BkBIov4R.js.map +0 -1
  37. package/dist/components-IdP8GXXX.cjs +0 -461
  38. package/dist/components-IdP8GXXX.cjs.map +0 -1
  39. package/dist/fakerGenerator-CYUCNH3Q.cjs +0 -204
  40. package/dist/fakerGenerator-CYUCNH3Q.cjs.map +0 -1
  41. package/dist/fakerGenerator-M5oCrPmy.js +0 -200
  42. package/dist/fakerGenerator-M5oCrPmy.js.map +0 -1
  43. package/dist/types-r7BubMLO.d.ts +0 -132
  44. package/src/parser.ts +0 -453
package/src/utils.ts ADDED
@@ -0,0 +1,356 @@
1
+ import { posix } from 'node:path'
2
+ import { ast } from '@kubb/core'
3
+ import type { ResolverFaker } from './types.ts'
4
+
5
+ /**
6
+ * Returns the `@faker-js/faker` named export for a locale code.
7
+ *
8
+ * Without a locale, returns `'faker'` for the default English instance.
9
+ * With a locale, the language code is converted to upper case and joined with any region suffix.
10
+ *
11
+ * @example Default
12
+ * `localeToFakerImport() // 'faker'`
13
+ *
14
+ * @example Simple locale
15
+ * `localeToFakerImport('de') // 'fakerDE'`
16
+ *
17
+ * @example Compound locale
18
+ * `localeToFakerImport('de_AT') // 'fakerDE_AT'`
19
+ */
20
+ export function localeToFakerImport(locale?: string): string {
21
+ if (!locale) {
22
+ return 'faker'
23
+ }
24
+
25
+ const parts = locale.split('_')
26
+ parts[0] = parts[0]!.toUpperCase()
27
+ return `faker${parts.join('_')}`
28
+ }
29
+
30
+ /**
31
+ * Determines if a schema node can be overridden during faker generation.
32
+ */
33
+ export function canOverrideSchema(node: ast.SchemaNode): boolean {
34
+ return new Set<ast.SchemaNode['type']>([
35
+ 'array',
36
+ 'tuple',
37
+ 'object',
38
+ 'intersection',
39
+ 'union',
40
+ 'enum',
41
+ 'ref',
42
+ 'string',
43
+ 'email',
44
+ 'url',
45
+ 'uuid',
46
+ 'number',
47
+ 'integer',
48
+ 'bigint',
49
+ 'boolean',
50
+ 'date',
51
+ 'time',
52
+ 'datetime',
53
+ 'blob',
54
+ ]).has(node.type)
55
+ }
56
+
57
+ /**
58
+ * Resolves a schema reference by looking up the referenced schema in the provided array.
59
+ * Returns the original node if it's not a reference.
60
+ */
61
+ export function resolveSchemaRef(node: ast.SchemaNode, schemas: Array<ast.SchemaNode>): ast.SchemaNode {
62
+ if (node.type !== 'ref') {
63
+ return node
64
+ }
65
+
66
+ return schemas.find((schema) => schema.name === node.name && schema.type !== 'ref') ?? node
67
+ }
68
+
69
+ /**
70
+ * Resolves a parameter name based on its location (path, query, header, etc.) using the provided resolver.
71
+ */
72
+ export function resolveParamNameByLocation(
73
+ resolver: Pick<ResolverFaker, 'resolvePathParamsName' | 'resolveQueryParamsName' | 'resolveHeaderParamsName' | 'resolveParamName'>,
74
+ node: ast.OperationNode,
75
+ param: ast.ParameterNode,
76
+ ): string {
77
+ switch (param.in) {
78
+ case 'path':
79
+ return resolver.resolvePathParamsName(node, param)
80
+ case 'query':
81
+ return resolver.resolveQueryParamsName(node, param)
82
+ case 'header':
83
+ return resolver.resolveHeaderParamsName(node, param)
84
+ default:
85
+ return resolver.resolveParamName(node, param)
86
+ }
87
+ }
88
+
89
+ function shouldInlineSingleResponseSchema(schema: ast.SchemaNode): boolean {
90
+ return new Set<ast.SchemaNode['type']>([
91
+ 'any',
92
+ 'unknown',
93
+ 'void',
94
+ 'null',
95
+ 'array',
96
+ 'tuple',
97
+ 'string',
98
+ 'email',
99
+ 'url',
100
+ 'uuid',
101
+ 'number',
102
+ 'integer',
103
+ 'bigint',
104
+ 'boolean',
105
+ 'date',
106
+ 'time',
107
+ 'datetime',
108
+ 'blob',
109
+ 'enum',
110
+ 'union',
111
+ ]).has(schema.type)
112
+ }
113
+
114
+ /**
115
+ * Builds a response schema as a union of all response statuses.
116
+ * Returns null if no responses are provided, or embeds single simple responses inline.
117
+ */
118
+ export function buildResponseUnionSchema(node: ast.OperationNode, resolver: ResolverFaker): ast.SchemaNode | null {
119
+ const responses = node.responses.filter((response) => response.schema)
120
+
121
+ if (!responses.length) {
122
+ return null
123
+ }
124
+
125
+ if (responses.length === 1) {
126
+ if (shouldInlineSingleResponseSchema(responses[0]!.schema)) {
127
+ return responses[0]!.schema
128
+ }
129
+
130
+ return ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, responses[0]!.statusCode) })
131
+ }
132
+
133
+ return ast.createSchema({
134
+ type: 'union',
135
+ members: responses.map((response) => ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, response.statusCode) })),
136
+ })
137
+ }
138
+
139
+ /**
140
+ * Import name that can be a string or a renamed import object.
141
+ */
142
+ export type ImportName = string | { propertyName: string; name?: string }
143
+
144
+ /**
145
+ * Import entry containing module path and imported names.
146
+ */
147
+ export type ImportEntry = {
148
+ name: string | Array<ImportName>
149
+ path: string
150
+ }
151
+
152
+ const SCALAR_TYPES = new Set<ast.SchemaNode['type']>([
153
+ 'string',
154
+ 'email',
155
+ 'url',
156
+ 'uuid',
157
+ 'number',
158
+ 'integer',
159
+ 'bigint',
160
+ 'boolean',
161
+ 'date',
162
+ 'time',
163
+ 'datetime',
164
+ 'blob',
165
+ 'enum',
166
+ ])
167
+ const ARRAY_TYPES = new Set<ast.SchemaNode['type']>(['array'])
168
+
169
+ function toRelativeImportPath(from: string, to: string): string {
170
+ const relativePath = posix.relative(posix.dirname(from), to)
171
+ return relativePath.startsWith('../') ? relativePath : `./${relativePath}`
172
+ }
173
+
174
+ function escapeRegExp(value: string): string {
175
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
176
+ }
177
+
178
+ /**
179
+ * Filters imports to only those that are actually used in the generated code.
180
+ * Checks for function calls matching the imported names.
181
+ */
182
+ export function filterUsedImports(imports: Array<ImportEntry>, text: string, skipImportNames: Array<string> = []): Array<ImportEntry> {
183
+ return imports.filter((entry) => {
184
+ const names = (Array.isArray(entry.name) ? entry.name : [entry.name])
185
+ .map((name) => {
186
+ if (typeof name === 'string') {
187
+ return name
188
+ }
189
+
190
+ return name?.name ?? name?.propertyName
191
+ })
192
+ .filter((name): name is string => Boolean(name))
193
+
194
+ return names.some((name) => {
195
+ if (skipImportNames.includes(name)) {
196
+ return false
197
+ }
198
+
199
+ return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text)
200
+ })
201
+ })
202
+ }
203
+
204
+ /**
205
+ * Detects and resolves import name conflicts by adding aliases to conflicting names.
206
+ * Returns updated imports with a mapping of original names to their aliases.
207
+ */
208
+ export function aliasConflictingImports(
209
+ imports: Array<ImportEntry>,
210
+ reservedNames: Iterable<string>,
211
+ ): { imports: Array<ImportEntry>; aliases: Map<string, string> } {
212
+ const reservedNameSet = new Set(reservedNames)
213
+ const aliases = new Map<string, string>()
214
+
215
+ const aliasedImports = imports.map((entry) => {
216
+ const names = Array.isArray(entry.name) ? entry.name : [entry.name]
217
+ const aliasedNames = names.map((item): ImportName => {
218
+ if (typeof item !== 'string' || !reservedNameSet.has(item)) {
219
+ return item
220
+ }
221
+
222
+ const alias = `${item}Schema`
223
+ aliases.set(item, alias)
224
+
225
+ return { propertyName: item, name: alias }
226
+ })
227
+
228
+ return aliasedNames.some((item) => typeof item === 'object' && item.name)
229
+ ? {
230
+ ...entry,
231
+ name: aliasedNames,
232
+ }
233
+ : entry
234
+ })
235
+
236
+ return {
237
+ imports: aliasedImports,
238
+ aliases,
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Replaces all occurrences of original names with their aliased versions in the given text.
244
+ */
245
+ export function rewriteAliasedImports(text: string, aliases: ReadonlyMap<string, string>): string {
246
+ return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, 'g'), alias), text)
247
+ }
248
+
249
+ /**
250
+ * Resolves a type reference, determining if it needs an import statement or inline type reference.
251
+ * Takes into account whether the type can be overridden and the file paths.
252
+ */
253
+ export function resolveTypeReference({
254
+ node,
255
+ canOverride,
256
+ name,
257
+ typeName,
258
+ filePath,
259
+ typeFilePath,
260
+ }: {
261
+ node: ast.SchemaNode
262
+ canOverride: boolean
263
+ name: string
264
+ typeName: string
265
+ filePath: string
266
+ typeFilePath: string
267
+ }): { importPath?: string; typeName: string } {
268
+ const { usesTypeName } = resolveFakerTypeUsage(node, typeName, canOverride)
269
+
270
+ if (!usesTypeName) {
271
+ return { typeName }
272
+ }
273
+
274
+ if (name === typeName) {
275
+ return {
276
+ typeName: `import('${toRelativeImportPath(filePath, typeFilePath)}').${typeName}`,
277
+ }
278
+ }
279
+
280
+ return {
281
+ importPath: typeFilePath,
282
+ typeName,
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Maps a schema node type to its corresponding scalar type representation.
288
+ * Returns the type name for enums or the base type (string, number, etc.) for primitives.
289
+ */
290
+ export function getScalarType(node: ast.SchemaNode, typeName: string): string {
291
+ switch (node.type) {
292
+ case 'string':
293
+ case 'email':
294
+ case 'url':
295
+ case 'uuid':
296
+ return 'string'
297
+ case 'number':
298
+ case 'integer':
299
+ return 'number'
300
+ case 'bigint':
301
+ return 'bigint'
302
+ case 'boolean':
303
+ return 'boolean'
304
+ case 'date':
305
+ case 'time':
306
+ return node.representation === 'date' ? 'Date' : 'string'
307
+ case 'datetime':
308
+ return 'string'
309
+ case 'blob':
310
+ return 'Blob'
311
+ case 'enum':
312
+ return typeName
313
+ default:
314
+ return typeName
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Resolves faker type usage information for a schema.
320
+ * Determines the data type, return type, and whether it uses the type name.
321
+ */
322
+ export function resolveFakerTypeUsage(
323
+ node: ast.SchemaNode,
324
+ typeName: string,
325
+ canOverride: boolean,
326
+ ): {
327
+ dataType: string
328
+ returnType: string | undefined
329
+ usesTypeName: boolean
330
+ } {
331
+ const isArray = ARRAY_TYPES.has(node.type)
332
+ const isTuple = node.type === 'tuple'
333
+ const isScalar = SCALAR_TYPES.has(node.type)
334
+
335
+ let dataType = `Partial<${typeName}>`
336
+
337
+ if (isArray || isTuple || node.type === 'union' || node.type === 'enum') {
338
+ dataType = typeName
339
+ }
340
+
341
+ if (isScalar) {
342
+ dataType = getScalarType(node, typeName)
343
+ }
344
+
345
+ let returnType = canOverride ? typeName : undefined
346
+
347
+ if (isScalar) {
348
+ returnType = getScalarType(node, typeName)
349
+ }
350
+
351
+ return {
352
+ dataType,
353
+ returnType,
354
+ usesTypeName: dataType.includes(typeName) || Boolean(returnType?.includes(typeName)),
355
+ }
356
+ }