@kubb/plugin-faker 5.0.0-beta.64 → 5.0.0-beta.74

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.js CHANGED
@@ -1,10 +1,277 @@
1
- import "./chunk-C0LytTxp.js";
2
- import { t as Faker } from "./Faker-DUx7jvCo.js";
3
- import { n as printerFaker, t as fakerGenerator } from "./fakerGenerator-D5Bcy5WY.js";
4
- import { definePlugin, defineResolver } from "@kubb/core";
5
- import { pluginTsName } from "@kubb/plugin-ts";
6
- import path from "node:path";
1
+ import "./rolldown-runtime-C0LytTxp.js";
2
+ import { buildObject, containsCircularRef, extractRefName, jsStringEscape, mapSchemaItems, mapSchemaMembers, objectKey, stringify, toRegExpString } from "@kubb/ast/utils";
3
+ import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
+ import { File, Function, jsxRenderer } from "@kubb/renderer-jsx";
5
+ import path, { posix } from "node:path";
6
+ import { ast, defineGenerator, definePlugin, defineResolver } from "@kubb/core";
7
+ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
7
8
  import { createHash } from "node:crypto";
9
+ //#region src/utils.ts
10
+ /**
11
+ * Returns the `@faker-js/faker` named export for a locale code.
12
+ *
13
+ * Without a locale, returns `'faker'` for the default English instance.
14
+ * With a locale, the language code is converted to upper case and joined with any region suffix.
15
+ *
16
+ * @example Default
17
+ * `localeToFakerImport() // 'faker'`
18
+ *
19
+ * @example Simple locale
20
+ * `localeToFakerImport('de') // 'fakerDE'`
21
+ *
22
+ * @example Compound locale
23
+ * `localeToFakerImport('de_AT') // 'fakerDE_AT'`
24
+ */
25
+ function localeToFakerImport(locale) {
26
+ if (!locale) return "faker";
27
+ const parts = locale.split("_");
28
+ parts[0] = parts[0].toUpperCase();
29
+ return `faker${parts.join("_")}`;
30
+ }
31
+ /**
32
+ * Determines if a schema node can be overridden during faker generation.
33
+ */
34
+ function canOverrideSchema(node) {
35
+ return new Set([
36
+ "array",
37
+ "tuple",
38
+ "object",
39
+ "intersection",
40
+ "union",
41
+ "enum",
42
+ "ref",
43
+ "string",
44
+ "email",
45
+ "url",
46
+ "uuid",
47
+ "number",
48
+ "integer",
49
+ "bigint",
50
+ "boolean",
51
+ "date",
52
+ "time",
53
+ "datetime",
54
+ "blob"
55
+ ]).has(node.type);
56
+ }
57
+ /**
58
+ * Resolves a parameter name based on its location (path, query, header, etc.) using the provided resolver.
59
+ */
60
+ function resolveParamNameByLocation(resolver, node, param) {
61
+ switch (param.in) {
62
+ case "path": return resolver.resolvePathParamsName(node, param);
63
+ case "query": return resolver.resolveQueryParamsName(node, param);
64
+ case "header": return resolver.resolveHeaderParamsName(node, param);
65
+ default: return resolver.resolveParamName(node, param);
66
+ }
67
+ }
68
+ function shouldInlineSingleResponseSchema(schema) {
69
+ return new Set([
70
+ "any",
71
+ "unknown",
72
+ "void",
73
+ "null",
74
+ "array",
75
+ "tuple",
76
+ "string",
77
+ "email",
78
+ "url",
79
+ "uuid",
80
+ "number",
81
+ "integer",
82
+ "bigint",
83
+ "boolean",
84
+ "date",
85
+ "time",
86
+ "datetime",
87
+ "blob",
88
+ "enum",
89
+ "union"
90
+ ]).has(schema.type);
91
+ }
92
+ /**
93
+ * Builds a response schema as a union of all response statuses.
94
+ * Returns null if no responses are provided, or embeds single simple responses inline.
95
+ */
96
+ function buildResponseUnionSchema(node, resolver) {
97
+ const responses = node.responses.filter((response) => response.content?.[0]?.schema);
98
+ if (!responses.length) return null;
99
+ if (responses.length === 1) {
100
+ const schema = responses[0].content?.[0]?.schema;
101
+ if (schema && shouldInlineSingleResponseSchema(schema)) return schema;
102
+ return ast.factory.createSchema({
103
+ type: "ref",
104
+ name: resolver.resolveResponseStatusName(node, responses[0].statusCode)
105
+ });
106
+ }
107
+ return ast.factory.createSchema({
108
+ type: "union",
109
+ members: responses.map((response) => ast.factory.createSchema({
110
+ type: "ref",
111
+ name: resolver.resolveResponseStatusName(node, response.statusCode)
112
+ }))
113
+ });
114
+ }
115
+ const SCALAR_TYPES$1 = new Set([
116
+ "string",
117
+ "email",
118
+ "url",
119
+ "uuid",
120
+ "number",
121
+ "integer",
122
+ "bigint",
123
+ "boolean",
124
+ "date",
125
+ "time",
126
+ "datetime",
127
+ "blob",
128
+ "enum"
129
+ ]);
130
+ const ARRAY_TYPES$1 = new Set(["array"]);
131
+ function toRelativeImportPath(from, to) {
132
+ const relativePath = posix.relative(posix.dirname(from), to);
133
+ return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
134
+ }
135
+ /**
136
+ * Resolves a type reference, determining if it needs an import statement or inline type reference.
137
+ * Takes into account whether the type can be overridden and the file paths.
138
+ */
139
+ function resolveTypeReference({ node, canOverride, name, typeName, filePath, typeFilePath }) {
140
+ const { usesTypeName } = resolveFakerTypeUsage(node, typeName, canOverride);
141
+ if (!usesTypeName) return { typeName };
142
+ if (name === typeName) return { typeName: `import('${toRelativeImportPath(filePath, typeFilePath)}').${typeName}` };
143
+ return {
144
+ importPath: typeFilePath,
145
+ typeName
146
+ };
147
+ }
148
+ /**
149
+ * Maps a schema node type to its corresponding scalar type representation.
150
+ * Returns the type name for enums or the base type (string, number, etc.) for primitives.
151
+ */
152
+ function getScalarType(node, typeName) {
153
+ switch (node.type) {
154
+ case "string":
155
+ case "email":
156
+ case "url":
157
+ case "uuid": return "string";
158
+ case "number":
159
+ case "integer": return "number";
160
+ case "bigint": return "bigint";
161
+ case "boolean": return "boolean";
162
+ case "date":
163
+ case "time": return node.representation === "date" ? "Date" : "string";
164
+ case "datetime": return "string";
165
+ case "blob": return "Blob";
166
+ case "enum": return typeName;
167
+ default: return typeName;
168
+ }
169
+ }
170
+ /**
171
+ * Resolves faker type usage information for a schema.
172
+ * Determines the data type, return type, and whether it uses the type name.
173
+ */
174
+ function resolveFakerTypeUsage(node, typeName, canOverride) {
175
+ const isArray = ARRAY_TYPES$1.has(node.type);
176
+ const isTuple = node.type === "tuple";
177
+ const isScalar = SCALAR_TYPES$1.has(node.type);
178
+ let dataType = `Partial<${typeName}>`;
179
+ if (isArray || isTuple || node.type === "union" || node.type === "enum") dataType = typeName;
180
+ if (isScalar) dataType = getScalarType(node, typeName);
181
+ let returnType = canOverride ? typeName : null;
182
+ if (isScalar) returnType = getScalarType(node, typeName);
183
+ return {
184
+ dataType,
185
+ returnType,
186
+ usesTypeName: dataType.includes(typeName) || Boolean(returnType?.includes(typeName))
187
+ };
188
+ }
189
+ //#endregion
190
+ //#region src/components/Faker.tsx
191
+ const OBJECT_TYPES = new Set(["object", "intersection"]);
192
+ const ARRAY_TYPES = new Set(["array"]);
193
+ const SCALAR_TYPES = new Set([
194
+ "string",
195
+ "email",
196
+ "url",
197
+ "uuid",
198
+ "number",
199
+ "integer",
200
+ "bigint",
201
+ "boolean",
202
+ "date",
203
+ "time",
204
+ "datetime",
205
+ "blob",
206
+ "enum"
207
+ ]);
208
+ const declarationPrinter = functionPrinter({ mode: "declaration" });
209
+ function Faker({ node, description, name, typeName, printer, seed, canOverride }) {
210
+ const fakerText = printer.print(node) ?? "undefined";
211
+ const isArray = ARRAY_TYPES.has(node.type);
212
+ const isObject = OBJECT_TYPES.has(node.type);
213
+ const isTuple = node.type === "tuple";
214
+ const isScalar = SCALAR_TYPES.has(node.type);
215
+ const useGenericOverride = canOverride && isObject;
216
+ const fakerTextWithOverride = (() => {
217
+ if (canOverride && isTuple) return `data || ${fakerText}`;
218
+ if (canOverride && isArray) return `[\n ...${fakerText},\n ...(data || [])\n]`;
219
+ if (canOverride && isScalar) return `data ?? ${fakerText}`;
220
+ return fakerText;
221
+ })();
222
+ const { dataType, returnType: resolvedReturnType } = resolveFakerTypeUsage(node, typeName, canOverride);
223
+ if (!useGenericOverride) {
224
+ const params = createFunctionParameters({ params: [createFunctionParameter({
225
+ name: /\bdata\b/.test(fakerTextWithOverride) ? "data" : "_data",
226
+ type: dataType,
227
+ optional: true
228
+ })] });
229
+ const paramsSignature = declarationPrinter.print(params) ?? "";
230
+ const returnType = resolvedReturnType;
231
+ const returnExpression = node.type === "ref" && canOverride && returnType ? `${fakerTextWithOverride} as ${returnType}` : fakerTextWithOverride;
232
+ return /* @__PURE__ */ jsx(File.Source, {
233
+ name,
234
+ isExportable: true,
235
+ isIndexable: true,
236
+ children: /* @__PURE__ */ jsxs(Function, {
237
+ export: true,
238
+ name,
239
+ JSDoc: { comments: description ? [`@description ${jsStringEscape(description)}`] : [] },
240
+ params: canOverride ? paramsSignature : void 0,
241
+ returnType: returnType ?? void 0,
242
+ children: [seed ? /* @__PURE__ */ jsxs(Fragment, { children: [`faker.seed(${JSON.stringify(seed)})`, /* @__PURE__ */ jsx("br", {})] }) : void 0, `return ${returnExpression}`]
243
+ })
244
+ });
245
+ }
246
+ const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
247
+ const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
248
+ const { cyclicSchemas, schemaName } = printer.options;
249
+ const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => containsCircularRef(p.schema, {
250
+ circularSchemas: cyclicSchemas,
251
+ excludeName: schemaName
252
+ })) ? `{
253
+ ${seedCode}const defaultFakeData = ${fakerText}
254
+ if (data) {
255
+ for (const [key, value] of Object.entries(data)) {
256
+ Object.defineProperty(defaultFakeData, key, { value, configurable: true, writable: true, enumerable: true })
257
+ }
258
+ }
259
+ return defaultFakeData as Omit<typeof defaultFakeData, keyof TData> & TData
260
+ }` : `{
261
+ ${seedCode}const defaultFakeData = ${fakerText}
262
+ return {
263
+ ...defaultFakeData,
264
+ ...(data || {}),
265
+ } as Omit<typeof defaultFakeData, keyof TData> & TData
266
+ }`;
267
+ return /* @__PURE__ */ jsxs(File.Source, {
268
+ name,
269
+ isExportable: true,
270
+ isIndexable: true,
271
+ children: [functionSignature, functionBody]
272
+ });
273
+ }
274
+ //#endregion
8
275
  //#region ../../internals/utils/src/casing.ts
9
276
  /**
10
277
  * Shared implementation for camelCase and PascalCase conversion.
@@ -57,6 +324,51 @@ function toFilePath(name, caseLast = camelCase) {
57
324
  return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
58
325
  }
59
326
  //#endregion
327
+ //#region ../../internals/utils/src/imports.ts
328
+ function escapeRegExp(value) {
329
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
330
+ }
331
+ function getImportNames(entry) {
332
+ return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
333
+ if (typeof name === "string") return name;
334
+ return name.name ?? name.propertyName;
335
+ }).filter((name) => Boolean(name));
336
+ }
337
+ function filterUsedImports(imports, text, skipImportNames = []) {
338
+ const skip = new Set(skipImportNames);
339
+ return imports.filter((entry) => {
340
+ return getImportNames(entry).some((name) => {
341
+ if (skip.has(name)) return false;
342
+ return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
343
+ });
344
+ });
345
+ }
346
+ function aliasConflictingImports(imports, reservedNames) {
347
+ const reservedNameSet = new Set(reservedNames);
348
+ const aliases = /* @__PURE__ */ new Map();
349
+ return {
350
+ imports: imports.map((entry) => {
351
+ const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
352
+ if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
353
+ const alias = `${item}Schema`;
354
+ aliases.set(item, alias);
355
+ return {
356
+ propertyName: item,
357
+ name: alias
358
+ };
359
+ });
360
+ return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
361
+ ...entry,
362
+ name: aliasedNames
363
+ } : entry;
364
+ }),
365
+ aliases
366
+ };
367
+ }
368
+ function rewriteAliasedImports(text, aliases) {
369
+ return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
370
+ }
371
+ //#endregion
60
372
  //#region ../../internals/utils/src/reserved.ts
61
373
  /**
62
374
  * JavaScript and Java reserved words.
@@ -180,6 +492,77 @@ function ensureValidVarName(name) {
180
492
  return `_${name}`;
181
493
  }
182
494
  //#endregion
495
+ //#region ../../internals/shared/src/params.ts
496
+ const caseParamsCache = /* @__PURE__ */ new WeakMap();
497
+ /**
498
+ * Applies camelCase to parameter names and returns a new array without mutating the input.
499
+ *
500
+ * Run it before handing parameters to schema builders so output property keys get the right casing
501
+ * while `OperationNode.parameters` stays intact for other consumers. When `casing` is unset, the
502
+ * original array is returned unchanged. Results are cached per input array.
503
+ */
504
+ function caseParams(params, casing) {
505
+ if (!casing) return params;
506
+ const cached = caseParamsCache.get(params);
507
+ if (cached) return cached;
508
+ const result = params.map((param) => ({
509
+ ...param,
510
+ name: camelCase(param.name)
511
+ }));
512
+ caseParamsCache.set(params, result);
513
+ return result;
514
+ }
515
+ //#endregion
516
+ //#region ../../internals/shared/src/operation.ts
517
+ /**
518
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
519
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
520
+ */
521
+ function getContentTypeSuffix(contentType) {
522
+ const baseType = contentType.split(";")[0].trim();
523
+ if (baseType === "application/json") return "Json";
524
+ if (baseType === "multipart/form-data") return "FormData";
525
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
526
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
527
+ if (parts.length === 0) return "Unknown";
528
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
529
+ }
530
+ /**
531
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
532
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
533
+ */
534
+ function getPerContentTypeName(baseName, suffix) {
535
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
536
+ return baseName + suffix;
537
+ }
538
+ /**
539
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
540
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
541
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
542
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
543
+ */
544
+ function resolveContentTypeVariants(entries, baseName) {
545
+ const usedNames = /* @__PURE__ */ new Set();
546
+ return entries.filter((entry) => entry.schema).map((entry) => {
547
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
548
+ let suffix = baseSuffix;
549
+ let name = getPerContentTypeName(baseName, suffix);
550
+ let counter = 2;
551
+ while (usedNames.has(name)) {
552
+ suffix = `${baseSuffix}${counter++}`;
553
+ name = getPerContentTypeName(baseName, suffix);
554
+ }
555
+ usedNames.add(name);
556
+ return {
557
+ name,
558
+ suffix,
559
+ schema: entry.schema,
560
+ keysToOmit: entry.keysToOmit,
561
+ contentType: entry.contentType
562
+ };
563
+ });
564
+ }
565
+ //#endregion
183
566
  //#region ../../internals/shared/src/group.ts
184
567
  /**
185
568
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -211,6 +594,560 @@ function createGroupConfig(group) {
211
594
  };
212
595
  }
213
596
  //#endregion
597
+ //#region src/printers/printerFaker.ts
598
+ const fakerKeywordMapper = {
599
+ any: () => "undefined",
600
+ unknown: () => "undefined",
601
+ void: () => "undefined",
602
+ number: (min, max) => {
603
+ if (max !== void 0 && min !== void 0) return `faker.number.float({ min: ${min}, max: ${max} })`;
604
+ if (max !== void 0) return `faker.number.float({ max: ${max} })`;
605
+ if (min !== void 0) return `faker.number.float({ min: ${min} })`;
606
+ return "faker.number.float()";
607
+ },
608
+ integer: (min, max) => {
609
+ if (max !== void 0 && min !== void 0) return `faker.number.int({ min: ${min}, max: ${max} })`;
610
+ if (max !== void 0) return `faker.number.int({ max: ${max} })`;
611
+ if (min !== void 0) return `faker.number.int({ min: ${min} })`;
612
+ return "faker.number.int()";
613
+ },
614
+ bigint: () => "faker.number.bigInt()",
615
+ string: (min, max) => {
616
+ if (max !== void 0 && min !== void 0) return `faker.string.alpha({ length: { min: ${min}, max: ${max} } })`;
617
+ if (max !== void 0) return `faker.string.alpha({ length: ${max} })`;
618
+ if (min !== void 0) return `faker.string.alpha({ length: ${min} })`;
619
+ return "faker.string.alpha()";
620
+ },
621
+ boolean: () => "faker.datatype.boolean()",
622
+ null: () => "null",
623
+ array: (items = [], min, max) => {
624
+ if (items.length > 1) return `faker.helpers.arrayElements([${items.join(", ")}])`;
625
+ const item = items.at(0);
626
+ if (min !== void 0 && max !== void 0) return `faker.helpers.multiple(() => (${item}), { count: { min: ${min}, max: ${max} }})`;
627
+ if (min !== void 0) return `faker.helpers.multiple(() => (${item}), { count: ${min} })`;
628
+ if (max !== void 0) return `faker.helpers.multiple(() => (${item}), { count: { min: 0, max: ${max} }})`;
629
+ return `faker.helpers.multiple(() => (${item}))`;
630
+ },
631
+ tuple: (items = []) => `[${items.join(", ")}]`,
632
+ enum: (items = [], type = "any") => `faker.helpers.arrayElement<${type}>([${items.join(", ")}])`,
633
+ union: (items = []) => `faker.helpers.arrayElement<any>([${items.join(", ")}])`,
634
+ datetime: () => "faker.date.anytime().toISOString()",
635
+ date: (representation = "string", parser = "faker") => {
636
+ if (representation === "string") {
637
+ if (parser !== "faker") return `${parser}(faker.date.anytime()).format("YYYY-MM-DD")`;
638
+ return "faker.date.anytime().toISOString().substring(0, 10)";
639
+ }
640
+ if (parser !== "faker") throw new Error(`type '${representation}' and parser '${parser}' can not work together`);
641
+ return "faker.date.anytime()";
642
+ },
643
+ time: (representation = "string", parser = "faker") => {
644
+ if (representation === "string") {
645
+ if (parser !== "faker") return `${parser}(faker.date.anytime()).format("HH:mm:ss")`;
646
+ return "faker.date.anytime().toISOString().substring(11, 19)";
647
+ }
648
+ if (parser !== "faker") throw new Error(`type '${representation}' and parser '${parser}' can not work together`);
649
+ return "faker.date.anytime()";
650
+ },
651
+ uuid: () => "faker.string.uuid()",
652
+ url: () => "faker.internet.url()",
653
+ and: (items = []) => {
654
+ if (items.length === 0) return "{}";
655
+ if (items.length === 1) return items[0] ?? "{}";
656
+ return `{...${items.join(", ...")}}`;
657
+ },
658
+ matches: (value = "", regexGenerator = "faker") => {
659
+ if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
660
+ return `faker.helpers.fromRegExp("${value}")`;
661
+ },
662
+ email: () => "faker.internet.email()",
663
+ blob: () => "faker.image.url() as unknown as Blob"
664
+ };
665
+ function getEnumValues(node) {
666
+ if (node.namedEnumValues?.length) return node.namedEnumValues.map((item) => item.value);
667
+ return node.enumValues ?? [];
668
+ }
669
+ function parseEnumValue(value) {
670
+ if (typeof value === "string") return stringify(value);
671
+ return value;
672
+ }
673
+ /** Reads the discriminator literal off a variant, or `undefined` when it can't be determined. */
674
+ function getDiscriminatorValue(member, discriminatorPropertyName) {
675
+ const prop = ast.narrowSchema(member, "object")?.properties?.find((p) => p.name === discriminatorPropertyName);
676
+ const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
677
+ return enumNode ? getEnumValues(enumNode)[0] : void 0;
678
+ }
679
+ /**
680
+ * Type expression for an object property's value, indexed off the parent `typeName`.
681
+ *
682
+ * In a union (`oneOf`), a key that only some branches declare turns a plain `NonNullable<T>[K]`
683
+ * into a TS2339 error, so union members guard the access. The breakdown is below.
684
+ */
685
+ function indexedTypeName(typeName, propertyName, nestedInUnion) {
686
+ const key = JSON.stringify(propertyName);
687
+ return nestedInUnion ? `(NonNullable<${typeName}> & Record<${key}, unknown>)[${key}]` : `NonNullable<${typeName}>[${key}]`;
688
+ }
689
+ /**
690
+ * Creates a Faker printer that generates mock data generation code from schema nodes.
691
+ * Handles circular references gracefully by emitting memoizing getters for cyclic properties.
692
+ */
693
+ const printerFaker = ast.createPrinter((options) => {
694
+ const printNested = (node, overrideOptions = {}) => {
695
+ return printerFaker({
696
+ ...options,
697
+ ...overrideOptions,
698
+ nodes: options.nodes
699
+ }).print(node) ?? "undefined";
700
+ };
701
+ return {
702
+ name: "faker",
703
+ options,
704
+ nodes: {
705
+ any: () => fakerKeywordMapper.any(),
706
+ unknown: () => fakerKeywordMapper.unknown(),
707
+ void: () => fakerKeywordMapper.void(),
708
+ boolean: () => fakerKeywordMapper.boolean(),
709
+ null: () => fakerKeywordMapper.null(),
710
+ string(node) {
711
+ if (node.pattern) return fakerKeywordMapper.matches(node.pattern, this.options.regexGenerator);
712
+ return fakerKeywordMapper.string(node.min, node.max);
713
+ },
714
+ email: () => fakerKeywordMapper.email(),
715
+ url: () => fakerKeywordMapper.url(),
716
+ uuid: () => fakerKeywordMapper.uuid(),
717
+ number(node) {
718
+ return fakerKeywordMapper.number(node.min, node.max);
719
+ },
720
+ integer(node) {
721
+ return fakerKeywordMapper.integer(node.min, node.max);
722
+ },
723
+ bigint: () => fakerKeywordMapper.bigint(),
724
+ blob: () => fakerKeywordMapper.blob(),
725
+ datetime: () => fakerKeywordMapper.datetime(),
726
+ date(node) {
727
+ return fakerKeywordMapper.date(node.representation ?? "string", this.options.dateParser);
728
+ },
729
+ time(node) {
730
+ return fakerKeywordMapper.time(node.representation ?? "string", this.options.dateParser);
731
+ },
732
+ ref(node) {
733
+ const refName = node.ref ? extractRefName(node.ref) ?? node.name ?? node.schema?.name : node.name ?? node.schema?.name;
734
+ if (!refName) throw new Error("Name not defined for ref node");
735
+ if (this.options.schemaName && refName === this.options.schemaName) return "undefined as any";
736
+ const resolvedName = node.ref ? this.options.resolver.resolveName(refName) : refName;
737
+ if (!this.options.nestedInObject) return `${resolvedName}(data)`;
738
+ return `${resolvedName}()`;
739
+ },
740
+ enum(node) {
741
+ return fakerKeywordMapper.enum(getEnumValues(node).map(parseEnumValue), this.options.typeName);
742
+ },
743
+ union(node) {
744
+ const { discriminatorPropertyName } = node;
745
+ const baseTypeName = this.options.typeName;
746
+ const items = mapSchemaMembers(node, (member) => {
747
+ const value = discriminatorPropertyName ? getDiscriminatorValue(member, discriminatorPropertyName) : void 0;
748
+ if (baseTypeName && value !== void 0) return printNested(member, {
749
+ typeName: `Extract<NonNullable<${baseTypeName}>, { ${JSON.stringify(discriminatorPropertyName)}: ${parseEnumValue(value)} }>`,
750
+ nestedInObject: true
751
+ });
752
+ return printNested(member, {
753
+ typeName: baseTypeName,
754
+ nestedInObject: true,
755
+ nestedInUnion: true
756
+ });
757
+ }).map(({ output }) => output).filter((item) => Boolean(item));
758
+ return fakerKeywordMapper.union(items);
759
+ },
760
+ intersection(node) {
761
+ const items = mapSchemaMembers(node, (member) => printNested(member, { nestedInObject: true })).map(({ output }) => output).filter((item) => Boolean(item) && item !== "undefined");
762
+ return fakerKeywordMapper.and(items);
763
+ },
764
+ array(node) {
765
+ const items = mapSchemaItems(node, (member) => printNested(member, {
766
+ typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[number]` : void 0,
767
+ nestedInObject: true
768
+ })).map(({ output }) => output).filter((item) => Boolean(item));
769
+ return fakerKeywordMapper.array(items, node.min, node.max);
770
+ },
771
+ tuple(node) {
772
+ const items = (node.items ?? []).map((member, index) => printNested(member, {
773
+ typeName: this.options.typeName ? `NonNullable<${this.options.typeName}>[${index}]` : void 0,
774
+ nestedInObject: true
775
+ })).filter((item) => Boolean(item));
776
+ return fakerKeywordMapper.tuple(items);
777
+ },
778
+ object(node) {
779
+ const cyclicSchemas = this.options.cyclicSchemas;
780
+ return buildObject((node.properties ?? []).map((property) => {
781
+ if (this.options.mapper && Object.hasOwn(this.options.mapper, property.name)) return `${objectKey(property.name)}: ${this.options.mapper[property.name]}`;
782
+ const value = printNested(property.schema, {
783
+ typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
784
+ nestedInObject: true
785
+ }) ?? "undefined";
786
+ if (cyclicSchemas && containsCircularRef(property.schema, {
787
+ circularSchemas: cyclicSchemas,
788
+ excludeName: this.options.schemaName
789
+ })) return `get ${objectKey(property.name)}() { const _value = ${value}; Object.defineProperty(this, ${JSON.stringify(property.name)}, { value: _value, configurable: true, writable: true, enumerable: true }); return _value }`;
790
+ return `${objectKey(property.name)}: ${value}`;
791
+ }));
792
+ },
793
+ ...options.nodes
794
+ },
795
+ print(node) {
796
+ return this.transform(node) ?? null;
797
+ }
798
+ };
799
+ });
800
+ //#endregion
801
+ //#region src/generators/fakerGenerator.tsx
802
+ /**
803
+ * Built-in generator for `@kubb/plugin-faker`. Emits one `createX` factory
804
+ * per schema in the spec plus per-operation request/response factories. Each
805
+ * factory returns a value matching the corresponding TypeScript type from
806
+ * `@kubb/plugin-ts`.
807
+ */
808
+ const fakerGenerator = defineGenerator({
809
+ name: "faker",
810
+ renderer: jsxRenderer,
811
+ schema(node, ctx) {
812
+ const { adapter, config, resolver, root } = ctx;
813
+ const { output, group, dateParser, regexGenerator, mapper, seed, locale, printer } = ctx.options;
814
+ const pluginTs = ctx.driver.getPlugin(pluginTsName);
815
+ if (!node.name || !pluginTs) return;
816
+ const tsResolver = ctx.driver.getResolver(pluginTsName);
817
+ const schemaName = node.name;
818
+ const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
819
+ const tsEnumType = pluginTs.options?.enum?.type;
820
+ const tsEnumTypeSuffix = pluginTs.options?.enum?.typeSuffix ?? "Key";
821
+ const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.resolveEnumKeyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.resolveTypeName(schemaName);
822
+ const meta = {
823
+ name: resolver.resolveName(schemaName),
824
+ file: resolver.resolveFile({
825
+ name: schemaName,
826
+ extname: ".ts"
827
+ }, {
828
+ root,
829
+ output,
830
+ group: group ?? void 0
831
+ }),
832
+ typeName: schemaTypeName,
833
+ typeFile: tsResolver.resolveFile({
834
+ name: schemaName,
835
+ extname: ".ts"
836
+ }, {
837
+ root,
838
+ output: pluginTs.options?.output ?? output,
839
+ group: pluginTs.options?.group ?? void 0
840
+ })
841
+ };
842
+ const canOverride = canOverrideSchema(node);
843
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
844
+ const printerInstance = printerFaker({
845
+ resolver,
846
+ schemaName,
847
+ typeName: meta.typeName,
848
+ dateParser,
849
+ regexGenerator,
850
+ mapper,
851
+ nodes: printer?.nodes,
852
+ cyclicSchemas
853
+ });
854
+ const fakerText = printerInstance.print(node) ?? "undefined";
855
+ const typeReference = resolveTypeReference({
856
+ node,
857
+ canOverride,
858
+ name: meta.name,
859
+ typeName: meta.typeName,
860
+ filePath: meta.file.path,
861
+ typeFilePath: meta.typeFile.path
862
+ });
863
+ const usedImports = filterUsedImports(adapter.getImports(node, (schemaName) => ({
864
+ name: resolver.resolveName(schemaName),
865
+ path: resolver.resolveFile({
866
+ name: schemaName,
867
+ extname: ".ts"
868
+ }, {
869
+ root,
870
+ output,
871
+ group: group ?? void 0
872
+ }).path
873
+ })), fakerText);
874
+ return /* @__PURE__ */ jsxs(File, {
875
+ baseName: meta.file.baseName,
876
+ path: meta.file.path,
877
+ meta: meta.file.meta,
878
+ banner: resolver.resolveBanner(ctx.meta, {
879
+ output,
880
+ config,
881
+ file: {
882
+ path: meta.file.path,
883
+ baseName: meta.file.baseName
884
+ }
885
+ }),
886
+ footer: resolver.resolveFooter(ctx.meta, {
887
+ output,
888
+ config,
889
+ file: {
890
+ path: meta.file.path,
891
+ baseName: meta.file.baseName
892
+ }
893
+ }),
894
+ children: [
895
+ /* @__PURE__ */ jsx(File.Import, {
896
+ name: locale ? [{
897
+ propertyName: localeToFakerImport(locale),
898
+ name: "faker"
899
+ }] : ["faker"],
900
+ path: "@faker-js/faker"
901
+ }),
902
+ regexGenerator === "randexp" && /* @__PURE__ */ jsx(File.Import, {
903
+ name: "RandExp",
904
+ path: "randexp"
905
+ }),
906
+ dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
907
+ path: dateParser,
908
+ name: dateParser
909
+ }),
910
+ typeReference.importPath && /* @__PURE__ */ jsx(File.Import, {
911
+ isTypeOnly: true,
912
+ root: meta.file.path,
913
+ path: typeReference.importPath,
914
+ name: [meta.typeName]
915
+ }),
916
+ usedImports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
917
+ root: meta.file.path,
918
+ path: imp.path,
919
+ name: imp.name
920
+ }, [
921
+ schemaName,
922
+ imp.path,
923
+ imp.name
924
+ ].join("-"))),
925
+ /* @__PURE__ */ jsx(Faker, {
926
+ name: meta.name,
927
+ typeName: typeReference.typeName,
928
+ description: node.description,
929
+ node,
930
+ printer: printerInstance,
931
+ seed,
932
+ canOverride
933
+ })
934
+ ]
935
+ });
936
+ },
937
+ operation(node, ctx) {
938
+ const { adapter, config, resolver, root } = ctx;
939
+ const { output, group, dateParser, regexGenerator, mapper, seed, locale, printer } = ctx.options;
940
+ const pluginTs = ctx.driver.getPlugin(pluginTsName);
941
+ if (!pluginTs) return;
942
+ const tsResolver = ctx.driver.getResolver(pluginTsName);
943
+ const paramEntries = caseParams(node.parameters, "camelcase").map((param) => ({
944
+ param,
945
+ name: resolveParamNameByLocation(resolver, node, param),
946
+ typeName: resolveParamNameByLocation(tsResolver, node, param)
947
+ }));
948
+ function expandContentUnits(entries, baseName, tsBaseName, description, decorate) {
949
+ const withSchema = entries.filter((entry) => entry.schema);
950
+ if (withSchema.length <= 1) {
951
+ const primary = withSchema[0] ?? entries[0];
952
+ if (!primary?.schema) return [];
953
+ return [{
954
+ schema: decorate ? decorate(primary.schema) : primary.schema,
955
+ name: baseName,
956
+ typeName: tsBaseName,
957
+ description,
958
+ skipImportNames: []
959
+ }];
960
+ }
961
+ const variants = resolveContentTypeVariants(entries, baseName);
962
+ const unionSchema = ast.factory.createSchema({
963
+ type: "union",
964
+ members: variants.map((variant) => ast.factory.createSchema({
965
+ type: "ref",
966
+ name: variant.name
967
+ }))
968
+ });
969
+ return [...variants.map((variant) => ({
970
+ schema: decorate ? decorate(variant.schema) : variant.schema,
971
+ name: variant.name,
972
+ typeName: getPerContentTypeName(tsBaseName, variant.suffix),
973
+ description,
974
+ skipImportNames: []
975
+ })), {
976
+ schema: unionSchema,
977
+ name: baseName,
978
+ typeName: tsBaseName,
979
+ description,
980
+ skipImportNames: variants.map((variant) => variant.name)
981
+ }];
982
+ }
983
+ const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.resolveResponseStatusName(node, response.statusCode), tsResolver.resolveResponseStatusName(node, response.statusCode), response.description));
984
+ const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.resolveDataName(node), tsResolver.resolveDataName(node), node.requestBody?.description, (schema) => ({
985
+ ...schema,
986
+ description: node.requestBody?.description ?? schema.description
987
+ }));
988
+ const responseName = resolver.resolveResponseName(node);
989
+ const localHelperNames = new Set([
990
+ ...paramEntries.map((entry) => entry.name),
991
+ ...responseUnits.map((unit) => unit.name),
992
+ ...dataUnits.map((unit) => unit.name),
993
+ responseName
994
+ ]);
995
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
996
+ const meta = {
997
+ file: resolver.resolveFile({
998
+ name: node.operationId,
999
+ extname: ".ts",
1000
+ tag: node.tags[0] ?? "default",
1001
+ path: node.path
1002
+ }, {
1003
+ root,
1004
+ output,
1005
+ group: group ?? void 0
1006
+ }),
1007
+ typeFile: tsResolver.resolveFile({
1008
+ name: node.operationId,
1009
+ extname: ".ts",
1010
+ tag: node.tags[0] ?? "default",
1011
+ path: node.path
1012
+ }, {
1013
+ root,
1014
+ output: pluginTs.options?.output ?? output,
1015
+ group: pluginTs.options?.group ?? void 0
1016
+ })
1017
+ };
1018
+ function resolveMockImports(schema) {
1019
+ return adapter.getImports(schema, (schemaName) => ({
1020
+ name: resolver.resolveName(schemaName),
1021
+ path: resolver.resolveFile({
1022
+ name: schemaName,
1023
+ extname: ".ts"
1024
+ }, {
1025
+ root,
1026
+ output,
1027
+ group: group ?? void 0
1028
+ }).path
1029
+ })).filter((entry) => entry.path !== meta.file.path);
1030
+ }
1031
+ function renderEntry({ schema, name, typeName, description, skipImportNames = [] }) {
1032
+ if (!schema) return null;
1033
+ const canOverride = canOverrideSchema(schema);
1034
+ const printerInstance = printerFaker({
1035
+ resolver,
1036
+ schemaName: name,
1037
+ typeName,
1038
+ dateParser,
1039
+ regexGenerator,
1040
+ mapper,
1041
+ nodes: printer?.nodes,
1042
+ cyclicSchemas
1043
+ });
1044
+ const fakerText = printerInstance.print(schema) ?? "undefined";
1045
+ const { imports, aliases } = aliasConflictingImports(filterUsedImports(resolveMockImports(schema), fakerText, skipImportNames), localHelperNames);
1046
+ const rewrittenFakerText = rewriteAliasedImports(fakerText, aliases);
1047
+ const typeReference = resolveTypeReference({
1048
+ node: schema,
1049
+ canOverride,
1050
+ name,
1051
+ typeName,
1052
+ filePath: meta.file.path,
1053
+ typeFilePath: meta.typeFile.path
1054
+ });
1055
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1056
+ typeReference.importPath && /* @__PURE__ */ jsx(File.Import, {
1057
+ isTypeOnly: true,
1058
+ root: meta.file.path,
1059
+ path: typeReference.importPath,
1060
+ name: [typeName]
1061
+ }),
1062
+ imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1063
+ root: meta.file.path,
1064
+ path: imp.path,
1065
+ name: imp.name
1066
+ }, [
1067
+ name,
1068
+ imp.path,
1069
+ imp.name
1070
+ ].join("-"))),
1071
+ /* @__PURE__ */ jsx(Faker, {
1072
+ name,
1073
+ typeName: typeReference.typeName,
1074
+ description,
1075
+ node: schema,
1076
+ printer: {
1077
+ ...printerInstance,
1078
+ print: () => rewrittenFakerText
1079
+ },
1080
+ seed,
1081
+ canOverride
1082
+ })
1083
+ ] });
1084
+ }
1085
+ return /* @__PURE__ */ jsxs(File, {
1086
+ baseName: meta.file.baseName,
1087
+ path: meta.file.path,
1088
+ meta: meta.file.meta,
1089
+ banner: resolver.resolveBanner(ctx.meta, {
1090
+ output,
1091
+ config,
1092
+ file: {
1093
+ path: meta.file.path,
1094
+ baseName: meta.file.baseName
1095
+ }
1096
+ }),
1097
+ footer: resolver.resolveFooter(ctx.meta, {
1098
+ output,
1099
+ config,
1100
+ file: {
1101
+ path: meta.file.path,
1102
+ baseName: meta.file.baseName
1103
+ }
1104
+ }),
1105
+ children: [
1106
+ /* @__PURE__ */ jsx(File.Import, {
1107
+ name: locale ? [{
1108
+ propertyName: localeToFakerImport(locale),
1109
+ name: "faker"
1110
+ }] : ["faker"],
1111
+ path: "@faker-js/faker"
1112
+ }),
1113
+ regexGenerator === "randexp" && /* @__PURE__ */ jsx(File.Import, {
1114
+ name: "RandExp",
1115
+ path: "randexp"
1116
+ }),
1117
+ dateParser !== "faker" && /* @__PURE__ */ jsx(File.Import, {
1118
+ path: dateParser,
1119
+ name: dateParser
1120
+ }),
1121
+ paramEntries.map(({ param, name, typeName }) => renderEntry({
1122
+ schema: param.schema,
1123
+ name,
1124
+ typeName
1125
+ })),
1126
+ responseUnits.map((unit) => renderEntry({
1127
+ schema: unit.schema,
1128
+ name: unit.name,
1129
+ typeName: unit.typeName,
1130
+ description: unit.description,
1131
+ skipImportNames: unit.skipImportNames
1132
+ })),
1133
+ dataUnits.map((unit) => renderEntry({
1134
+ schema: unit.schema,
1135
+ name: unit.name,
1136
+ typeName: unit.typeName,
1137
+ description: unit.description,
1138
+ skipImportNames: unit.skipImportNames
1139
+ })),
1140
+ renderEntry({
1141
+ schema: buildResponseUnionSchema(node, resolver),
1142
+ name: responseName,
1143
+ typeName: tsResolver.resolveResponseName(node),
1144
+ skipImportNames: responseUnits.map((unit) => unit.name)
1145
+ })
1146
+ ]
1147
+ });
1148
+ }
1149
+ });
1150
+ //#endregion
214
1151
  //#region src/resolvers/resolverFaker.ts
215
1152
  /**
216
1153
  * Default resolver used by `@kubb/plugin-faker`. Decides the names and file
@@ -320,7 +1257,7 @@ const pluginFaker = definePlugin((options) => {
320
1257
  const { output = {
321
1258
  path: "mocks",
322
1259
  barrel: { type: "named" }
323
- }, seed, locale = "en", group, exclude = [], include, override = [], mapper = {}, dateParser = "faker", generators: userGenerators = [], regexGenerator = "faker", paramsCasing, printer, resolver: userResolver, macros: userMacros } = options;
1260
+ }, seed, locale = "en", group, exclude = [], include, override = [], mapper = {}, dateParser = "faker", regexGenerator = "faker", printer, resolver: userResolver, macros: userMacros } = options;
324
1261
  const groupConfig = createGroupConfig(group);
325
1262
  return {
326
1263
  name: pluginFakerName,
@@ -338,7 +1275,6 @@ const pluginFaker = definePlugin((options) => {
338
1275
  mapper,
339
1276
  dateParser,
340
1277
  regexGenerator,
341
- paramsCasing,
342
1278
  printer
343
1279
  });
344
1280
  ctx.setResolver(userResolver ? {
@@ -347,7 +1283,6 @@ const pluginFaker = definePlugin((options) => {
347
1283
  } : resolverFaker);
348
1284
  if (userMacros?.length) ctx.setMacros(userMacros);
349
1285
  ctx.addGenerator(fakerGenerator);
350
- for (const generator of userGenerators) ctx.addGenerator(generator);
351
1286
  } }
352
1287
  };
353
1288
  });