@kubb/plugin-faker 5.0.0-beta.10 → 5.0.0-beta.103

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.
@@ -1,153 +0,0 @@
1
- import { jsStringEscape } from '@internals/utils'
2
- import { ast } from '@kubb/core'
3
- import { functionPrinter } from '@kubb/plugin-ts'
4
- import { File, Function } from '@kubb/renderer-jsx'
5
- import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
- import type { PrinterFakerFactory } from '../printers/printerFaker.ts'
7
- import type { PluginFaker } from '../types.ts'
8
- import { resolveFakerTypeUsage } from '../utils.ts'
9
-
10
- type Props = {
11
- name: string
12
- typeName: string
13
- node: ast.SchemaNode
14
- printer: ast.Printer<PrinterFakerFactory>
15
- seed?: PluginFaker['options']['seed']
16
- description?: string
17
- canOverride: boolean
18
- }
19
-
20
- const OBJECT_TYPES = new Set<ast.SchemaNode['type']>(['object', 'intersection'])
21
- const ARRAY_TYPES = new Set<ast.SchemaNode['type']>(['array'])
22
- const SCALAR_TYPES = new Set<ast.SchemaNode['type']>([
23
- 'string',
24
- 'email',
25
- 'url',
26
- 'uuid',
27
- 'number',
28
- 'integer',
29
- 'bigint',
30
- 'boolean',
31
- 'date',
32
- 'time',
33
- 'datetime',
34
- 'blob',
35
- 'enum',
36
- ])
37
- const declarationPrinter = functionPrinter({ mode: 'declaration' })
38
-
39
- export function Faker({ node, description, name, typeName, printer, seed, canOverride }: Props): KubbReactNode {
40
- const fakerText = printer.print(node) ?? 'undefined'
41
-
42
- const isArray = ARRAY_TYPES.has(node.type)
43
- const isObject = OBJECT_TYPES.has(node.type)
44
- const isTuple = node.type === 'tuple'
45
- const isScalar = SCALAR_TYPES.has(node.type)
46
-
47
- let fakerTextWithOverride = fakerText
48
- let useGenericOverride = false
49
-
50
- if (canOverride && isObject) {
51
- useGenericOverride = true
52
- }
53
-
54
- if (canOverride && isTuple) {
55
- fakerTextWithOverride = `data || ${fakerText}`
56
- }
57
-
58
- if (canOverride && isArray) {
59
- fakerTextWithOverride = `[
60
- ...${fakerText},
61
- ...(data || [])
62
- ]`
63
- }
64
-
65
- if (canOverride && isScalar) {
66
- fakerTextWithOverride = `data ?? ${fakerText}`
67
- }
68
-
69
- const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride)
70
-
71
- let functionSignature = ''
72
- let functionBody = ''
73
-
74
- if (useGenericOverride) {
75
- // Generate function with defaultFakeData structure
76
- const jsdoc = description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ''
77
- functionSignature = `${jsdoc}export function ${name}(data?: Partial<${typeName}>): Required<${typeName}>`
78
-
79
- const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : ''
80
-
81
- // When the object node has properties that transitively reference a cyclic schema,
82
- // the printer emits memoizing getters for those properties. Spreading the object
83
- // literal would immediately invoke those getters, triggering recursive faker calls
84
- // and causing a stack overflow. Detect this upfront via ast helpers so we can
85
- // use Object.defineProperty-based merging instead of spread.
86
- const { cyclicSchemas, schemaName } = printer.options
87
- const hasGetters =
88
- node.type === 'object' &&
89
- !!cyclicSchemas &&
90
- (node.properties ?? []).some((p) => ast.containsCircularRef(p.schema, { circularSchemas: cyclicSchemas, excludeName: schemaName }))
91
-
92
- if (hasGetters) {
93
- functionBody = `{
94
- ${seedCode}const defaultFakeData = ${fakerText}
95
- if (data) {
96
- for (const [key, value] of Object.entries(data)) {
97
- Object.defineProperty(defaultFakeData, key, { value, configurable: true, writable: true, enumerable: true })
98
- }
99
- }
100
- return defaultFakeData as Required<${typeName}>
101
- }`
102
- } else {
103
- functionBody = `{
104
- ${seedCode}const defaultFakeData = ${fakerText}
105
- return {
106
- ...defaultFakeData,
107
- ...(data || {}),
108
- } as Required<${typeName}>
109
- }`
110
- }
111
- } else {
112
- const usesData = /\bdata\b/.test(fakerTextWithOverride)
113
- const dataParamName = usesData ? 'data' : '_data'
114
- const params = ast.createFunctionParameters({
115
- params: [
116
- ast.createFunctionParameter({
117
- name: dataParamName,
118
- type: ast.createParamsType({ variant: 'reference', name: dataType }),
119
- optional: true,
120
- }),
121
- ],
122
- })
123
- const paramsSignature = declarationPrinter.print(params) ?? ''
124
- const returnType = resolvedReturnType
125
-
126
- return (
127
- <File.Source name={name} isExportable isIndexable>
128
- <Function
129
- export
130
- name={name}
131
- JSDoc={{ comments: description ? [`@description ${jsStringEscape(description)}`] : [] }}
132
- params={canOverride ? paramsSignature : undefined}
133
- returnType={returnType}
134
- >
135
- {seed ? (
136
- <>
137
- {`faker.seed(${JSON.stringify(seed)})`}
138
- <br />
139
- </>
140
- ) : undefined}
141
- {`return ${fakerTextWithOverride}`}
142
- </Function>
143
- </File.Source>
144
- )
145
- }
146
-
147
- return (
148
- <File.Source name={name} isExportable isIndexable>
149
- {functionSignature}
150
- {functionBody}
151
- </File.Source>
152
- )
153
- }
@@ -1 +0,0 @@
1
- export { Faker } from './Faker.tsx'
@@ -1,269 +0,0 @@
1
- import { aliasConflictingImports, filterUsedImports, rewriteAliasedImports } from '@internals/utils'
2
- import { ast, defineGenerator } from '@kubb/core'
3
- import { pluginTsName } from '@kubb/plugin-ts'
4
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
- import { Faker } from '../components/Faker.tsx'
6
- import { printerFaker } from '../printers/printerFaker.ts'
7
- import type { PluginFaker } from '../types.ts'
8
- import {
9
- buildResponseUnionSchema,
10
- canOverrideSchema,
11
- localeToFakerImport,
12
- resolveParamNameByLocation,
13
- resolveSchemaRef,
14
- resolveTypeReference,
15
- } from '../utils.ts'
16
-
17
- export const fakerGenerator = defineGenerator<PluginFaker>({
18
- name: 'faker',
19
- renderer: jsxRenderer,
20
- schema(node, ctx) {
21
- const { adapter, config, resolver, root } = ctx
22
- const { output, group, dateParser, regexGenerator, mapper, seed, locale, printer } = ctx.options
23
- const pluginTs = ctx.driver.getPlugin(pluginTsName)
24
-
25
- if (!node.name || !pluginTs || !adapter.inputNode) {
26
- return
27
- }
28
-
29
- const tsResolver = ctx.driver.getResolver(pluginTsName)
30
-
31
- const schemaNode = resolveSchemaRef(node, adapter.inputNode.schemas)
32
- const schemaName = schemaNode.name ?? node.name
33
- const mode = ctx.getMode(output)
34
- const meta = {
35
- name: resolver.resolveName(schemaName),
36
- file: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }),
37
- typeName: tsResolver.resolveTypeName(schemaName),
38
- typeFile: tsResolver.resolveFile(
39
- { name: schemaName, extname: '.ts' },
40
- { root, output: pluginTs.options?.output ?? output, group: pluginTs.options?.group },
41
- ),
42
- } as const
43
- const canOverride = canOverrideSchema(schemaNode)
44
- const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
45
- const printerInstance = printerFaker({
46
- resolver,
47
- schemaName,
48
- typeName: meta.typeName,
49
- dateParser,
50
- regexGenerator,
51
- mapper,
52
- nodes: printer?.nodes,
53
- cyclicSchemas,
54
- })
55
- const fakerText = printerInstance.print(schemaNode) ?? 'undefined'
56
- const typeReference = resolveTypeReference({
57
- node: schemaNode,
58
- canOverride,
59
- name: meta.name,
60
- typeName: meta.typeName,
61
- filePath: meta.file.path,
62
- typeFilePath: meta.typeFile.path,
63
- })
64
-
65
- const imports = adapter
66
- .getImports(schemaNode, (schemaName) => ({
67
- name: resolver.resolveName(schemaName),
68
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
69
- }))
70
- .filter((entry) => entry.path !== meta.file.path)
71
- const usedImports = filterUsedImports(imports, fakerText)
72
-
73
- return (
74
- <File
75
- baseName={meta.file.baseName}
76
- path={meta.file.path}
77
- meta={meta.file.meta}
78
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
79
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
80
- >
81
- <File.Import name={locale ? [{ propertyName: localeToFakerImport(locale), name: 'faker' }] : ['faker']} path="@faker-js/faker" />
82
- {regexGenerator === 'randexp' && <File.Import name={'RandExp'} path={'randexp'} />}
83
- {dateParser !== 'faker' && <File.Import path={dateParser} name={dateParser} />}
84
- {typeReference.importPath && <File.Import isTypeOnly root={meta.file.path} path={typeReference.importPath} name={[meta.typeName]} />}
85
- {mode === 'split' &&
86
- usedImports.map((imp) => <File.Import key={[schemaName, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
87
- <Faker
88
- name={meta.name}
89
- typeName={typeReference.typeName}
90
- description={schemaNode.description}
91
- node={schemaNode}
92
- printer={printerInstance}
93
- seed={seed}
94
- canOverride={canOverride}
95
- />
96
- </File>
97
- )
98
- },
99
- operation(node, ctx) {
100
- const { adapter, config, resolver, root } = ctx
101
- const { output, group, paramsCasing, dateParser, regexGenerator, mapper, seed, locale, printer } = ctx.options
102
- const pluginTs = ctx.driver.getPlugin(pluginTsName)
103
-
104
- if (!pluginTs) {
105
- return
106
- }
107
-
108
- const tsResolver = ctx.driver.getResolver(pluginTsName)
109
-
110
- const params = ast.caseParams(node.parameters, paramsCasing)
111
- const paramEntries = params.map((param) => ({
112
- param,
113
- name: resolveParamNameByLocation(resolver, node, param),
114
- typeName: resolveParamNameByLocation(tsResolver, node, param),
115
- }))
116
- const responseEntries = node.responses.map((response) => ({
117
- response,
118
- name: resolver.resolveResponseStatusName(node, response.statusCode),
119
- typeName: tsResolver.resolveResponseStatusName(node, response.statusCode),
120
- }))
121
- const dataEntry = node.requestBody?.content?.[0]?.schema
122
- ? {
123
- schema: {
124
- ...node.requestBody.content![0]!.schema!,
125
- description: node.requestBody.description ?? node.requestBody.content![0]!.schema!.description,
126
- },
127
- name: resolver.resolveDataName(node),
128
- typeName: tsResolver.resolveDataName(node),
129
- description: node.requestBody.description ?? node.requestBody.content![0]!.schema!.description,
130
- }
131
- : null
132
- const responseName = resolver.resolveResponseName(node)
133
- const localHelperNames = new Set([
134
- ...paramEntries.map((entry) => entry.name),
135
- ...responseEntries.map((entry) => entry.name),
136
- ...(dataEntry ? [dataEntry.name] : []),
137
- responseName,
138
- ])
139
- const meta = {
140
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
141
- typeFile: tsResolver.resolveFile(
142
- {
143
- name: node.operationId,
144
- extname: '.ts',
145
- tag: node.tags[0] ?? 'default',
146
- path: node.path,
147
- },
148
- {
149
- root,
150
- output: pluginTs.options?.output ?? output,
151
- group: pluginTs.options?.group,
152
- },
153
- ),
154
- } as const
155
-
156
- function resolveMockImports(schema: ast.SchemaNode) {
157
- return adapter
158
- .getImports(schema, (schemaName) => ({
159
- name: resolver.resolveName(schemaName),
160
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
161
- }))
162
- .filter((entry) => entry.path !== meta.file.path)
163
- }
164
-
165
- function renderEntry({
166
- schema,
167
- name,
168
- typeName,
169
- description,
170
- skipImportNames = [],
171
- }: {
172
- schema: ast.SchemaNode | null
173
- name: string
174
- typeName: string
175
- description?: string
176
- skipImportNames?: Array<string>
177
- }) {
178
- if (!schema) {
179
- return null
180
- }
181
-
182
- const canOverride = canOverrideSchema(schema)
183
- const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
184
- const printerInstance = printerFaker({
185
- resolver,
186
- schemaName: name,
187
- typeName,
188
- dateParser,
189
- regexGenerator,
190
- mapper,
191
- nodes: printer?.nodes,
192
- cyclicSchemas,
193
- })
194
- const fakerText = printerInstance.print(schema) ?? 'undefined'
195
- const usedImports = filterUsedImports(resolveMockImports(schema), fakerText, skipImportNames)
196
- const { imports, aliases } = aliasConflictingImports(usedImports, localHelperNames)
197
- const rewrittenFakerText = rewriteAliasedImports(fakerText, aliases)
198
- const typeReference = resolveTypeReference({
199
- node: schema,
200
- canOverride,
201
- name,
202
- typeName,
203
- filePath: meta.file.path,
204
- typeFilePath: meta.typeFile.path,
205
- })
206
-
207
- return (
208
- <>
209
- {typeReference.importPath && <File.Import isTypeOnly root={meta.file.path} path={typeReference.importPath} name={[typeName]} />}
210
- {imports.map((imp) => (
211
- <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />
212
- ))}
213
- <Faker
214
- name={name}
215
- typeName={typeReference.typeName}
216
- description={description}
217
- node={schema}
218
- printer={{ ...printerInstance, print: () => rewrittenFakerText }}
219
- seed={seed}
220
- canOverride={canOverride}
221
- />
222
- </>
223
- )
224
- }
225
-
226
- return (
227
- <File
228
- baseName={meta.file.baseName}
229
- path={meta.file.path}
230
- meta={meta.file.meta}
231
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
232
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
233
- >
234
- <File.Import name={locale ? [{ propertyName: localeToFakerImport(locale), name: 'faker' }] : ['faker']} path="@faker-js/faker" />
235
- {regexGenerator === 'randexp' && <File.Import name={'RandExp'} path={'randexp'} />}
236
- {dateParser !== 'faker' && <File.Import path={dateParser} name={dateParser} />}
237
- {paramEntries.map(({ param, name, typeName }) =>
238
- renderEntry({
239
- schema: param.schema,
240
- name,
241
- typeName,
242
- }),
243
- )}
244
- {responseEntries.map(({ response, name, typeName }) =>
245
- renderEntry({
246
- schema: response.schema,
247
- name,
248
- typeName,
249
- description: response.description,
250
- }),
251
- )}
252
- {dataEntry
253
- ? renderEntry({
254
- schema: dataEntry.schema,
255
- name: dataEntry.name,
256
- typeName: dataEntry.typeName,
257
- description: dataEntry.description,
258
- })
259
- : null}
260
- {renderEntry({
261
- schema: buildResponseUnionSchema(node, resolver),
262
- name: responseName,
263
- typeName: tsResolver.resolveResponseName(node),
264
- skipImportNames: responseEntries.map(({ name }) => name),
265
- })}
266
- </File>
267
- )
268
- },
269
- })
@@ -1 +0,0 @@
1
- export { fakerGenerator } from './fakerGenerator.tsx'
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- export { Faker } from './components/Faker.tsx'
2
- export { fakerGenerator } from './generators/fakerGenerator.tsx'
3
- export { default, pluginFaker, pluginFakerName } from './plugin.ts'
4
- export type { PrinterFakerFactory, PrinterFakerNodes, PrinterFakerOptions } from './printers/printerFaker.ts'
5
- export { printerFaker } from './printers/printerFaker.ts'
6
- export { resolverFaker } from './resolvers/resolverFaker.ts'
7
- export type { PluginFaker, ResolverFaker } from './types.ts'
package/src/plugin.ts DELETED
@@ -1,88 +0,0 @@
1
- import { camelCase } from '@internals/utils'
2
- import { definePlugin, type Group } from '@kubb/core'
3
- import { pluginTsName } from '@kubb/plugin-ts'
4
- import { fakerGenerator } from './generators/fakerGenerator.tsx'
5
- import { resolverFaker } from './resolvers/resolverFaker.ts'
6
- import type { PluginFaker } from './types.ts'
7
-
8
- /**
9
- * Canonical plugin name for `@kubb/plugin-faker`, used in driver lookups and warnings.
10
- */
11
- export const pluginFakerName = 'plugin-faker' satisfies PluginFaker['name']
12
-
13
- /**
14
- * Generates Faker mock data factories from OpenAPI/AST specification.
15
- *
16
- * Creates randomized test data and mock helpers from schema definitions.
17
- *
18
- * @example
19
- * `import pluginFaker from '@kubb/plugin-faker'; export default defineConfig({ plugins: [pluginFaker({ output: { path: 'mocks' } })], })`
20
- */
21
- export const pluginFaker = definePlugin<PluginFaker>((options) => {
22
- const {
23
- output = { path: 'mocks', barrelType: 'named' },
24
- seed,
25
- locale,
26
- group,
27
- exclude = [],
28
- include,
29
- override = [],
30
- mapper = {},
31
- dateParser = 'faker',
32
- generators: userGenerators = [],
33
- regexGenerator = 'faker',
34
- paramsCasing,
35
- printer,
36
- resolver: userResolver,
37
- transformer: userTransformer,
38
- } = options
39
-
40
- const groupConfig = group
41
- ? ({
42
- ...group,
43
- name: group.name
44
- ? group.name
45
- : (ctx: { group: string }) => {
46
- if (group.type === 'path') {
47
- return `${ctx.group.split('/')[1]}`
48
- }
49
-
50
- return `${camelCase(ctx.group)}Controller`
51
- },
52
- } satisfies Group)
53
- : undefined
54
-
55
- return {
56
- name: pluginFakerName,
57
- options,
58
- dependencies: [pluginTsName],
59
- hooks: {
60
- 'kubb:plugin:setup'(ctx) {
61
- ctx.setOptions({
62
- output,
63
- seed,
64
- locale,
65
- exclude,
66
- include,
67
- override,
68
- group: groupConfig,
69
- mapper,
70
- dateParser,
71
- regexGenerator,
72
- paramsCasing,
73
- printer,
74
- })
75
- ctx.setResolver(userResolver ? { ...resolverFaker, ...userResolver } : resolverFaker)
76
- if (userTransformer) {
77
- ctx.setTransformer(userTransformer)
78
- }
79
- ctx.addGenerator(fakerGenerator)
80
- for (const generator of userGenerators) {
81
- ctx.addGenerator(generator)
82
- }
83
- },
84
- },
85
- }
86
- })
87
-
88
- export default pluginFaker