@kubb/plugin-faker 5.0.0-beta.84 → 5.0.0-beta.86

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,379 @@
1
1
  import "./rolldown-runtime-C0LytTxp.js";
2
- import { ast, defineGenerator, definePlugin, defineResolver } from "kubb/kit";
3
- import { createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
2
+ import { posix } from "node:path";
3
+ import { Resolver, ast, createResolver, defineGenerator, definePlugin } from "kubb/kit";
4
+ import { buildParams, createFunctionParameter, createFunctionParameters, functionPrinter, pluginTsName } from "@kubb/plugin-ts";
4
5
  import { File, Function, jsxRenderer } from "kubb/jsx";
5
- import path, { posix } from "node:path";
6
6
  import { Fragment, jsx, jsxs } from "kubb/jsx/jsx-runtime";
7
- import { createHash } from "node:crypto";
7
+ //#region ../../internals/utils/src/casing.ts
8
+ /**
9
+ * Shared implementation for camelCase and PascalCase conversion.
10
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
11
+ * and capitalizes each word according to `pascal`.
12
+ *
13
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
14
+ */
15
+ function toCamelOrPascal(text, pascal) {
16
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
17
+ if (word.length > 1 && word === word.toUpperCase()) return word;
18
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
19
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
20
+ }
21
+ /**
22
+ * Converts `text` to camelCase.
23
+ *
24
+ * @example Word boundaries
25
+ * `camelCase('hello-world') // 'helloWorld'`
26
+ *
27
+ * @example With a prefix
28
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
29
+ */
30
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
31
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
32
+ }
33
+ //#endregion
34
+ //#region ../../internals/utils/src/reserved.ts
35
+ /**
36
+ * JavaScript and Java reserved words.
37
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
38
+ */
39
+ const reservedWords = /* @__PURE__ */ new Set([
40
+ "abstract",
41
+ "arguments",
42
+ "boolean",
43
+ "break",
44
+ "byte",
45
+ "case",
46
+ "catch",
47
+ "char",
48
+ "class",
49
+ "const",
50
+ "continue",
51
+ "debugger",
52
+ "default",
53
+ "delete",
54
+ "do",
55
+ "double",
56
+ "else",
57
+ "enum",
58
+ "eval",
59
+ "export",
60
+ "extends",
61
+ "false",
62
+ "final",
63
+ "finally",
64
+ "float",
65
+ "for",
66
+ "function",
67
+ "goto",
68
+ "if",
69
+ "implements",
70
+ "import",
71
+ "in",
72
+ "instanceof",
73
+ "int",
74
+ "interface",
75
+ "let",
76
+ "long",
77
+ "native",
78
+ "new",
79
+ "null",
80
+ "package",
81
+ "private",
82
+ "protected",
83
+ "public",
84
+ "return",
85
+ "short",
86
+ "static",
87
+ "super",
88
+ "switch",
89
+ "synchronized",
90
+ "this",
91
+ "throw",
92
+ "throws",
93
+ "transient",
94
+ "true",
95
+ "try",
96
+ "typeof",
97
+ "var",
98
+ "void",
99
+ "volatile",
100
+ "while",
101
+ "with",
102
+ "yield",
103
+ "Array",
104
+ "Date",
105
+ "hasOwnProperty",
106
+ "Infinity",
107
+ "isFinite",
108
+ "isNaN",
109
+ "isPrototypeOf",
110
+ "length",
111
+ "Math",
112
+ "name",
113
+ "NaN",
114
+ "Number",
115
+ "Object",
116
+ "prototype",
117
+ "String",
118
+ "toString",
119
+ "undefined",
120
+ "valueOf"
121
+ ]);
122
+ /**
123
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * isValidVarName('status') // true
128
+ * isValidVarName('class') // false (reserved word)
129
+ * isValidVarName('42foo') // false (starts with digit)
130
+ * ```
131
+ */
132
+ function isValidVarName(name) {
133
+ if (!name || reservedWords.has(name)) return false;
134
+ return isIdentifier(name);
135
+ }
136
+ /**
137
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
138
+ * otherwise prefixes it with `_` so the result is a valid identifier.
139
+ *
140
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
141
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
142
+ * variable, type, or function names.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * ensureValidVarName('409') // '_409'
147
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
148
+ * ensureValidVarName('Pet') // 'Pet'
149
+ * ensureValidVarName('class') // '_class'
150
+ * ```
151
+ */
152
+ function ensureValidVarName(name) {
153
+ if (!name || isValidVarName(name)) return name;
154
+ return `_${name}`;
155
+ }
156
+ /**
157
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
158
+ *
159
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
160
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
161
+ * deciding whether an object key needs quoting.
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * isIdentifier('name') // true
166
+ * isIdentifier('x-total')// false
167
+ * ```
168
+ */
169
+ function isIdentifier(name) {
170
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
171
+ }
172
+ //#endregion
173
+ //#region ../../internals/utils/src/strings.ts
174
+ /**
175
+ * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
176
+ * any backslash or single quote in the content.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * singleQuote('foo') // "'foo'"
181
+ * singleQuote("o'clock") // "'o\\'clock'"
182
+ * ```
183
+ */
184
+ function singleQuote(value) {
185
+ if (value === void 0 || value === null) return "''";
186
+ return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
187
+ }
188
+ /**
189
+ * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
190
+ * Returns the string unchanged when no balanced quote pair is found.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * trimQuotes('"hello"') // 'hello'
195
+ * trimQuotes('hello') // 'hello'
196
+ * ```
197
+ */
198
+ function trimQuotes(text) {
199
+ if (text.length >= 2) {
200
+ const first = text[0];
201
+ const last = text[text.length - 1];
202
+ if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
203
+ }
204
+ return text;
205
+ }
206
+ /**
207
+ * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
208
+ *
209
+ * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
210
+ * code matches the repo style without a formatter.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * stringify('hello') // "'hello'"
215
+ * stringify('"hello"') // "'hello'"
216
+ * ```
217
+ */
218
+ function stringify(value) {
219
+ if (value === void 0 || value === null) return "''";
220
+ return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
221
+ }
222
+ /**
223
+ * Escapes characters that are not allowed inside JS string literals, covering quotes, backslashes,
224
+ * and the Unicode line terminators U+2028 and U+2029.
225
+ *
226
+ * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4
227
+ *
228
+ * @example
229
+ * ```ts
230
+ * jsStringEscape('say "hi"\nbye') // 'say \\"hi\\"\\nbye'
231
+ * ```
232
+ */
233
+ function jsStringEscape(input) {
234
+ return `${input}`.replace(/["'\\\n\r\u2028\u2029]/g, (character) => {
235
+ switch (character) {
236
+ case "\"":
237
+ case "'":
238
+ case "\\": return `\\${character}`;
239
+ case "\n": return "\\n";
240
+ case "\r": return "\\r";
241
+ case "\u2028": return "\\u2028";
242
+ case "\u2029": return "\\u2029";
243
+ default: return "";
244
+ }
245
+ });
246
+ }
247
+ /**
248
+ * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
249
+ * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
250
+ * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
255
+ * toRegExpString('^(?im)foo', null) // '/^foo/im'
256
+ * ```
257
+ */
258
+ function toRegExpString(text, func = "RegExp") {
259
+ const raw = trimQuotes(text);
260
+ const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
261
+ const replacementTarget = match?.[1] ?? "";
262
+ const matchedFlags = match?.[2];
263
+ const cleaned = raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, "");
264
+ const { source, flags } = new RegExp(cleaned, matchedFlags);
265
+ if (func === null) return `/${source}/${flags}`;
266
+ return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
267
+ }
268
+ //#endregion
269
+ //#region ../../internals/utils/src/codegen.ts
270
+ const INDENT = " ";
271
+ /**
272
+ * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
273
+ */
274
+ function indentLines(text) {
275
+ if (!text) return "";
276
+ return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
277
+ }
278
+ /**
279
+ * Renders an object key, quoting it with single quotes only when it is not a valid identifier.
280
+ * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * objectKey('name') // 'name'
285
+ * objectKey('x-total') // "'x-total'"
286
+ * ```
287
+ */
288
+ function objectKey(name) {
289
+ return isIdentifier(name) ? name : singleQuote(name);
290
+ }
291
+ /**
292
+ * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
293
+ * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
294
+ * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
295
+ *
296
+ * @example
297
+ * ```ts
298
+ * buildObject(['id: z.number()', 'name: z.string()'])
299
+ * // '{\n id: z.number(),\n name: z.string(),\n}'
300
+ * ```
301
+ */
302
+ function buildObject(entries) {
303
+ if (entries.length === 0) return "{}";
304
+ return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
305
+ }
306
+ //#endregion
307
+ //#region ../../internals/utils/src/fs.ts
308
+ /**
309
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
310
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
311
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
312
+ *
313
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
314
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
315
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
316
+ * absolute path, letting generated files escape the configured output directory.
317
+ *
318
+ * @example Nested path from a dotted name
319
+ * `toFilePath('pet.petId') // 'pet/petId'`
320
+ *
321
+ * @example PascalCase the final segment
322
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
323
+ *
324
+ * @example Suffix applied to the final segment only
325
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
326
+ */
327
+ function toFilePath(name, caseLast = camelCase) {
328
+ const parts = name.split(/\.(?=[a-zA-Z])/);
329
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
330
+ }
331
+ //#endregion
332
+ //#region ../../internals/utils/src/imports.ts
333
+ function escapeRegExp(value) {
334
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
335
+ }
336
+ function getImportNames(entry) {
337
+ return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
338
+ if (typeof name === "string") return name;
339
+ return name.name ?? name.propertyName;
340
+ }).filter((name) => Boolean(name));
341
+ }
342
+ function filterUsedImports(imports, text, skipImportNames = []) {
343
+ const skip = new Set(skipImportNames);
344
+ return imports.filter((entry) => {
345
+ return getImportNames(entry).some((name) => {
346
+ if (skip.has(name)) return false;
347
+ return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
348
+ });
349
+ });
350
+ }
351
+ function aliasConflictingImports(imports, reservedNames) {
352
+ const reservedNameSet = new Set(reservedNames);
353
+ const aliases = /* @__PURE__ */ new Map();
354
+ return {
355
+ imports: imports.map((entry) => {
356
+ const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
357
+ if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
358
+ const alias = `${item}Schema`;
359
+ aliases.set(item, alias);
360
+ return {
361
+ propertyName: item,
362
+ name: alias
363
+ };
364
+ });
365
+ return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
366
+ ...entry,
367
+ name: aliasedNames
368
+ } : entry;
369
+ }),
370
+ aliases
371
+ };
372
+ }
373
+ function rewriteAliasedImports(text, aliases) {
374
+ return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
375
+ }
376
+ //#endregion
8
377
  //#region src/utils.ts
9
378
  /**
10
379
  * Returns the `@faker-js/faker` named export for a locale code.
@@ -53,17 +422,6 @@ function canOverrideSchema(node) {
53
422
  "blob"
54
423
  ])).has(node.type);
55
424
  }
56
- /**
57
- * Resolves a parameter name based on its location (path, query, header, etc.) using the provided resolver.
58
- */
59
- function resolveParamNameByLocation(resolver, node, param) {
60
- switch (param.in) {
61
- case "path": return resolver.resolvePathParamsName(node, param);
62
- case "query": return resolver.resolveQueryParamsName(node, param);
63
- case "header": return resolver.resolveHeaderParamsName(node, param);
64
- default: return resolver.resolveParamName(node, param);
65
- }
66
- }
67
425
  function shouldInlineSingleResponseSchema(schema) {
68
426
  return (/* @__PURE__ */ new Set([
69
427
  "any",
@@ -100,14 +458,14 @@ function buildResponseUnionSchema(node, resolver) {
100
458
  if (schema && shouldInlineSingleResponseSchema(schema)) return schema;
101
459
  return ast.factory.createSchema({
102
460
  type: "ref",
103
- name: resolver.resolveResponseStatusName(node, responses[0].statusCode)
461
+ name: resolver.response.status(node, responses[0].statusCode)
104
462
  });
105
463
  }
106
464
  return ast.factory.createSchema({
107
465
  type: "union",
108
466
  members: responses.map((response) => ast.factory.createSchema({
109
467
  type: "ref",
110
- name: resolver.resolveResponseStatusName(node, response.statusCode)
468
+ name: resolver.response.status(node, response.statusCode)
111
469
  }))
112
470
  });
113
471
  }
@@ -126,7 +484,6 @@ const SCALAR_TYPES$1 = /* @__PURE__ */ new Set([
126
484
  "blob",
127
485
  "enum"
128
486
  ]);
129
- const ARRAY_TYPES$1 = /* @__PURE__ */ new Set(["array"]);
130
487
  function toRelativeImportPath(from, to) {
131
488
  const relativePath = posix.relative(posix.dirname(from), to);
132
489
  return relativePath.startsWith("../") ? relativePath : `./${relativePath}`;
@@ -171,7 +528,7 @@ function getScalarType(node, typeName) {
171
528
  * Determines the data type, return type, and whether it uses the type name.
172
529
  */
173
530
  function resolveFakerTypeUsage(node, typeName, canOverride) {
174
- const isArray = ARRAY_TYPES$1.has(node.type);
531
+ const isArray = node.type === "array";
175
532
  const isTuple = node.type === "tuple";
176
533
  const isScalar = SCALAR_TYPES$1.has(node.type);
177
534
  let dataType = `Partial<${typeName}>`;
@@ -188,7 +545,6 @@ function resolveFakerTypeUsage(node, typeName, canOverride) {
188
545
  //#endregion
189
546
  //#region src/components/Faker.tsx
190
547
  const OBJECT_TYPES = /* @__PURE__ */ new Set(["object", "intersection"]);
191
- const ARRAY_TYPES = /* @__PURE__ */ new Set(["array"]);
192
548
  const SCALAR_TYPES = /* @__PURE__ */ new Set([
193
549
  "string",
194
550
  "email",
@@ -207,7 +563,7 @@ const SCALAR_TYPES = /* @__PURE__ */ new Set([
207
563
  const declarationPrinter = functionPrinter({ mode: "declaration" });
208
564
  function Faker({ node, description, name, typeName, printer, seed, canOverride }) {
209
565
  const fakerText = printer.print(node) ?? "undefined";
210
- const isArray = ARRAY_TYPES.has(node.type);
566
+ const isArray = node.type === "array";
211
567
  const isObject = OBJECT_TYPES.has(node.type);
212
568
  const isTuple = node.type === "tuple";
213
569
  const isScalar = SCALAR_TYPES.has(node.type);
@@ -235,14 +591,14 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
235
591
  children: /* @__PURE__ */ jsxs(Function, {
236
592
  export: true,
237
593
  name,
238
- JSDoc: { comments: description ? [`@description ${ast.jsStringEscape(description)}`] : [] },
594
+ JSDoc: { comments: description ? [`@description ${jsStringEscape(description)}`] : [] },
239
595
  params: canOverride ? paramsSignature : void 0,
240
596
  returnType: returnType ?? void 0,
241
597
  children: [seed ? /* @__PURE__ */ jsxs(Fragment, { children: [`faker.seed(${JSON.stringify(seed)})`, /* @__PURE__ */ jsx("br", {})] }) : void 0, `return ${returnExpression}`]
242
598
  })
243
599
  });
244
600
  }
245
- const functionSignature = `${description ? `/**\n * @description ${ast.jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
601
+ const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
246
602
  const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
247
603
  const { cyclicSchemas, schemaName } = printer.options;
248
604
  const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.containsCircularRef(p.schema, {
@@ -271,226 +627,6 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
271
627
  });
272
628
  }
273
629
  //#endregion
274
- //#region ../../internals/utils/src/casing.ts
275
- /**
276
- * Shared implementation for camelCase and PascalCase conversion.
277
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
278
- * and capitalizes each word according to `pascal`.
279
- *
280
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
281
- */
282
- function toCamelOrPascal(text, pascal) {
283
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
284
- if (word.length > 1 && word === word.toUpperCase()) return word;
285
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
286
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
287
- }
288
- /**
289
- * Converts `text` to camelCase.
290
- *
291
- * @example Word boundaries
292
- * `camelCase('hello-world') // 'helloWorld'`
293
- *
294
- * @example With a prefix
295
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
296
- */
297
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
298
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
299
- }
300
- //#endregion
301
- //#region ../../internals/utils/src/fs.ts
302
- /**
303
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
304
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
305
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
306
- *
307
- * Empty segments are dropped before joining. They arise when the name starts with a dot
308
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
309
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
310
- * absolute path, letting generated files escape the configured output directory.
311
- *
312
- * @example Nested path from a dotted name
313
- * `toFilePath('pet.petId') // 'pet/petId'`
314
- *
315
- * @example PascalCase the final segment
316
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
317
- *
318
- * @example Suffix applied to the final segment only
319
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
320
- */
321
- function toFilePath(name, caseLast = camelCase) {
322
- const parts = name.split(/\.(?=[a-zA-Z])/);
323
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
324
- }
325
- //#endregion
326
- //#region ../../internals/utils/src/imports.ts
327
- function escapeRegExp(value) {
328
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
329
- }
330
- function getImportNames(entry) {
331
- return (Array.isArray(entry.name) ? entry.name : [entry.name]).map((name) => {
332
- if (typeof name === "string") return name;
333
- return name.name ?? name.propertyName;
334
- }).filter((name) => Boolean(name));
335
- }
336
- function filterUsedImports(imports, text, skipImportNames = []) {
337
- const skip = new Set(skipImportNames);
338
- return imports.filter((entry) => {
339
- return getImportNames(entry).some((name) => {
340
- if (skip.has(name)) return false;
341
- return new RegExp(`\\b${escapeRegExp(name)}\\b(?=\\s*\\()`).test(text);
342
- });
343
- });
344
- }
345
- function aliasConflictingImports(imports, reservedNames) {
346
- const reservedNameSet = new Set(reservedNames);
347
- const aliases = /* @__PURE__ */ new Map();
348
- return {
349
- imports: imports.map((entry) => {
350
- const aliasedNames = (Array.isArray(entry.name) ? entry.name : [entry.name]).map((item) => {
351
- if (typeof item !== "string" || !reservedNameSet.has(item)) return item;
352
- const alias = `${item}Schema`;
353
- aliases.set(item, alias);
354
- return {
355
- propertyName: item,
356
- name: alias
357
- };
358
- });
359
- return aliasedNames.some((item) => typeof item === "object" && item.name) ? {
360
- ...entry,
361
- name: aliasedNames
362
- } : entry;
363
- }),
364
- aliases
365
- };
366
- }
367
- function rewriteAliasedImports(text, aliases) {
368
- return Array.from(aliases).reduce((acc, [name, alias]) => acc.replace(new RegExp(`\\b${escapeRegExp(name)}\\b`, "g"), alias), text);
369
- }
370
- //#endregion
371
- //#region ../../internals/utils/src/reserved.ts
372
- /**
373
- * JavaScript and Java reserved words.
374
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
375
- */
376
- const reservedWords = /* @__PURE__ */ new Set([
377
- "abstract",
378
- "arguments",
379
- "boolean",
380
- "break",
381
- "byte",
382
- "case",
383
- "catch",
384
- "char",
385
- "class",
386
- "const",
387
- "continue",
388
- "debugger",
389
- "default",
390
- "delete",
391
- "do",
392
- "double",
393
- "else",
394
- "enum",
395
- "eval",
396
- "export",
397
- "extends",
398
- "false",
399
- "final",
400
- "finally",
401
- "float",
402
- "for",
403
- "function",
404
- "goto",
405
- "if",
406
- "implements",
407
- "import",
408
- "in",
409
- "instanceof",
410
- "int",
411
- "interface",
412
- "let",
413
- "long",
414
- "native",
415
- "new",
416
- "null",
417
- "package",
418
- "private",
419
- "protected",
420
- "public",
421
- "return",
422
- "short",
423
- "static",
424
- "super",
425
- "switch",
426
- "synchronized",
427
- "this",
428
- "throw",
429
- "throws",
430
- "transient",
431
- "true",
432
- "try",
433
- "typeof",
434
- "var",
435
- "void",
436
- "volatile",
437
- "while",
438
- "with",
439
- "yield",
440
- "Array",
441
- "Date",
442
- "hasOwnProperty",
443
- "Infinity",
444
- "isFinite",
445
- "isNaN",
446
- "isPrototypeOf",
447
- "length",
448
- "Math",
449
- "name",
450
- "NaN",
451
- "Number",
452
- "Object",
453
- "prototype",
454
- "String",
455
- "toString",
456
- "undefined",
457
- "valueOf"
458
- ]);
459
- /**
460
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
461
- *
462
- * @example
463
- * ```ts
464
- * isValidVarName('status') // true
465
- * isValidVarName('class') // false (reserved word)
466
- * isValidVarName('42foo') // false (starts with digit)
467
- * ```
468
- */
469
- function isValidVarName(name) {
470
- if (!name || reservedWords.has(name)) return false;
471
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
472
- }
473
- /**
474
- * Returns `name` when it's a syntactically valid JavaScript variable name,
475
- * otherwise prefixes it with `_` so the result is a valid identifier.
476
- *
477
- * Useful for sanitizing OpenAPI schema names or operation IDs that start with
478
- * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
479
- * variable, type, or function names.
480
- *
481
- * @example
482
- * ```ts
483
- * ensureValidVarName('409') // '_409'
484
- * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
485
- * ensureValidVarName('Pet') // 'Pet'
486
- * ensureValidVarName('class') // '_class'
487
- * ```
488
- */
489
- function ensureValidVarName(name) {
490
- if (!name || isValidVarName(name)) return name;
491
- return `_${name}`;
492
- }
493
- //#endregion
494
630
  //#region ../../internals/shared/src/params.ts
495
631
  const caseParamsCache = /* @__PURE__ */ new WeakMap();
496
632
  /**
@@ -511,6 +647,23 @@ function caseParams(params, casing) {
511
647
  caseParamsCache.set(params, result);
512
648
  return result;
513
649
  }
650
+ /**
651
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
652
+ *
653
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
654
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
655
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
656
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
657
+ */
658
+ function dedupeByCasedName(params) {
659
+ const seen = /* @__PURE__ */ new Set();
660
+ return params.filter((param) => {
661
+ const key = camelCase(param.name);
662
+ if (seen.has(key)) return false;
663
+ seen.add(key);
664
+ return true;
665
+ });
666
+ }
514
667
  //#endregion
515
668
  //#region ../../internals/shared/src/operation.ts
516
669
  /**
@@ -561,6 +714,15 @@ function resolveContentTypeVariants(entries, baseName) {
561
714
  };
562
715
  });
563
716
  }
717
+ function getOperationParameters(node, options = {}) {
718
+ const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
719
+ return {
720
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
721
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
722
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
723
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
724
+ };
725
+ }
564
726
  //#endregion
565
727
  //#region ../../internals/shared/src/group.ts
566
728
  /**
@@ -655,7 +817,7 @@ const fakerKeywordMapper = {
655
817
  return `{...${items.join(", ...")}}`;
656
818
  },
657
819
  matches: (value = "", regexGenerator = "faker") => {
658
- if (regexGenerator === "randexp") return `${ast.toRegExpString(value, "RandExp")}.gen()`;
820
+ if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
659
821
  return `faker.helpers.fromRegExp("${value}")`;
660
822
  },
661
823
  email: () => "faker.internet.email()",
@@ -666,7 +828,7 @@ function getEnumValues(node) {
666
828
  return node.enumValues ?? [];
667
829
  }
668
830
  function parseEnumValue(value) {
669
- if (typeof value === "string") return ast.stringify(value);
831
+ if (typeof value === "string") return stringify(value);
670
832
  return value;
671
833
  }
672
834
  /**
@@ -734,7 +896,7 @@ const printerFaker = ast.createPrinter((options) => {
734
896
  const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? ast.extractRefName(node.ref) ?? node.name ?? node.schema?.name : node.name ?? node.schema?.name;
735
897
  if (!refName) throw new Error("Name not defined for ref node");
736
898
  if (this.options.schemaName && refName === this.options.schemaName) return this.options.typeName ? `undefined as unknown as ${this.options.typeName}` : "undefined as unknown";
737
- const resolvedName = node.ref ? this.options.resolver.resolveName(refName) : refName;
899
+ const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
738
900
  if (!this.options.nestedInObject) return `${resolvedName}(data)`;
739
901
  return `${resolvedName}()`;
740
902
  },
@@ -778,7 +940,7 @@ const printerFaker = ast.createPrinter((options) => {
778
940
  },
779
941
  object(node) {
780
942
  const cyclicSchemas = this.options.cyclicSchemas;
781
- const entries = (node.properties ?? []).map((property) => {
943
+ return buildObject((node.properties ?? []).map((property) => {
782
944
  const value = printNested(property.schema, {
783
945
  typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
784
946
  nestedInObject: true
@@ -786,10 +948,9 @@ const printerFaker = ast.createPrinter((options) => {
786
948
  if (cyclicSchemas && ast.containsCircularRef(property.schema, {
787
949
  circularSchemas: cyclicSchemas,
788
950
  excludeName: this.options.schemaName
789
- })) return `get ${ast.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 `${ast.objectKey(property.name)}: ${value}`;
791
- });
792
- return ast.buildObject(entries);
951
+ })) 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 }`;
952
+ return `${objectKey(property.name)}: ${value}`;
953
+ }));
793
954
  },
794
955
  ...options.nodes
795
956
  },
@@ -819,22 +980,20 @@ const fakerGenerator = defineGenerator({
819
980
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
820
981
  const tsEnumType = pluginTs.options?.enum?.type;
821
982
  const tsEnumTypeSuffix = pluginTs.options?.enum?.typeSuffix ?? "Key";
822
- const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.resolveEnumKeyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.resolveTypeName(schemaName);
983
+ const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.enum.keyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.name(schemaName);
823
984
  const meta = {
824
- name: resolver.resolveName(schemaName),
825
- file: resolver.resolveFile({
985
+ name: resolver.name(schemaName),
986
+ file: resolver.file({
826
987
  name: schemaName,
827
- extname: ".ts"
828
- }, {
988
+ extname: ".ts",
829
989
  root,
830
990
  output,
831
991
  group: group ?? void 0
832
992
  }),
833
993
  typeName: schemaTypeName,
834
- typeFile: tsResolver.resolveFile({
994
+ typeFile: tsResolver.file({
835
995
  name: schemaName,
836
- extname: ".ts"
837
- }, {
996
+ extname: ".ts",
838
997
  root,
839
998
  output: pluginTs.options?.output ?? output,
840
999
  group: pluginTs.options?.group ?? void 0
@@ -862,11 +1021,10 @@ const fakerGenerator = defineGenerator({
862
1021
  typeFilePath: meta.typeFile.path
863
1022
  });
864
1023
  const usedImports = filterUsedImports(adapter.getImports(node, (schemaName) => ({
865
- name: resolver.resolveName(schemaName),
866
- path: resolver.resolveFile({
1024
+ name: resolver.name(schemaName),
1025
+ path: resolver.file({
867
1026
  name: schemaName,
868
- extname: ".ts"
869
- }, {
1027
+ extname: ".ts",
870
1028
  root,
871
1029
  output,
872
1030
  group: group ?? void 0
@@ -876,7 +1034,7 @@ const fakerGenerator = defineGenerator({
876
1034
  baseName: meta.file.baseName,
877
1035
  path: meta.file.path,
878
1036
  meta: meta.file.meta,
879
- banner: resolver.resolveBanner(ctx.meta, {
1037
+ banner: resolver.default.banner(ctx.meta, {
880
1038
  output,
881
1039
  config,
882
1040
  file: {
@@ -884,7 +1042,7 @@ const fakerGenerator = defineGenerator({
884
1042
  baseName: meta.file.baseName
885
1043
  }
886
1044
  }),
887
- footer: resolver.resolveFooter(ctx.meta, {
1045
+ footer: resolver.default.footer(ctx.meta, {
888
1046
  output,
889
1047
  config,
890
1048
  file: {
@@ -941,10 +1099,31 @@ const fakerGenerator = defineGenerator({
941
1099
  const pluginTs = ctx.driver.getPlugin(pluginTsName);
942
1100
  if (!pluginTs) return;
943
1101
  const tsResolver = ctx.driver.getResolver(pluginTsName);
944
- const paramEntries = caseParams(node.parameters, "camelcase").map((param) => ({
945
- param,
946
- name: resolveParamNameByLocation(resolver, node, param),
947
- typeName: resolveParamNameByLocation(tsResolver, node, param)
1102
+ const params = caseParams(node.parameters, "camelcase");
1103
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1104
+ ...node,
1105
+ parameters: params
1106
+ }, { paramsCasing: "original" });
1107
+ const paramGroups = [
1108
+ {
1109
+ params: pathParams,
1110
+ name: resolver.param.path,
1111
+ typeName: tsResolver.param.path
1112
+ },
1113
+ {
1114
+ params: queryParams,
1115
+ name: resolver.param.query,
1116
+ typeName: tsResolver.param.query
1117
+ },
1118
+ {
1119
+ params: headerParams,
1120
+ name: resolver.param.headers,
1121
+ typeName: tsResolver.param.headers
1122
+ }
1123
+ ].filter((group) => group.params.length > 0).map((group) => ({
1124
+ schema: buildParams({ params: group.params }),
1125
+ name: group.name(node, group.params[0]),
1126
+ typeName: group.typeName(node, group.params[0])
948
1127
  }));
949
1128
  function expandContentUnits(entries, baseName, tsBaseName, description, decorate) {
950
1129
  const withSchema = entries.filter((entry) => entry.schema);
@@ -981,36 +1160,34 @@ const fakerGenerator = defineGenerator({
981
1160
  skipImportNames: variants.map((variant) => variant.name)
982
1161
  }];
983
1162
  }
984
- const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.resolveResponseStatusName(node, response.statusCode), tsResolver.resolveResponseStatusName(node, response.statusCode), response.description));
985
- const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.resolveDataName(node), tsResolver.resolveDataName(node), node.requestBody?.description, (schema) => ({
1163
+ const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.response.status(node, response.statusCode), tsResolver.response.status(node, response.statusCode), response.description));
1164
+ const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.response.body(node), tsResolver.response.body(node), node.requestBody?.description, (schema) => ({
986
1165
  ...schema,
987
1166
  description: node.requestBody?.description ?? schema.description
988
1167
  }));
989
- const responseName = resolver.resolveResponseName(node);
1168
+ const responseName = resolver.response.response(node);
990
1169
  const localHelperNames = /* @__PURE__ */ new Set([
991
- ...paramEntries.map((entry) => entry.name),
1170
+ ...paramGroups.map((group) => group.name),
992
1171
  ...responseUnits.map((unit) => unit.name),
993
1172
  ...dataUnits.map((unit) => unit.name),
994
1173
  responseName
995
1174
  ]);
996
1175
  const cyclicSchemas = new Set(ctx.meta.circularNames);
997
1176
  const meta = {
998
- file: resolver.resolveFile({
1177
+ file: resolver.file({
999
1178
  name: node.operationId,
1000
1179
  extname: ".ts",
1001
1180
  tag: node.tags[0] ?? "default",
1002
- path: node.path
1003
- }, {
1181
+ path: node.path,
1004
1182
  root,
1005
1183
  output,
1006
1184
  group: group ?? void 0
1007
1185
  }),
1008
- typeFile: tsResolver.resolveFile({
1186
+ typeFile: tsResolver.file({
1009
1187
  name: node.operationId,
1010
1188
  extname: ".ts",
1011
1189
  tag: node.tags[0] ?? "default",
1012
- path: node.path
1013
- }, {
1190
+ path: node.path,
1014
1191
  root,
1015
1192
  output: pluginTs.options?.output ?? output,
1016
1193
  group: pluginTs.options?.group ?? void 0
@@ -1018,11 +1195,10 @@ const fakerGenerator = defineGenerator({
1018
1195
  };
1019
1196
  function resolveMockImports(schema) {
1020
1197
  return adapter.getImports(schema, (schemaName) => ({
1021
- name: resolver.resolveName(schemaName),
1022
- path: resolver.resolveFile({
1198
+ name: resolver.name(schemaName),
1199
+ path: resolver.file({
1023
1200
  name: schemaName,
1024
- extname: ".ts"
1025
- }, {
1201
+ extname: ".ts",
1026
1202
  root,
1027
1203
  output,
1028
1204
  group: group ?? void 0
@@ -1087,7 +1263,7 @@ const fakerGenerator = defineGenerator({
1087
1263
  baseName: meta.file.baseName,
1088
1264
  path: meta.file.path,
1089
1265
  meta: meta.file.meta,
1090
- banner: resolver.resolveBanner(ctx.meta, {
1266
+ banner: resolver.default.banner(ctx.meta, {
1091
1267
  output,
1092
1268
  config,
1093
1269
  file: {
@@ -1095,7 +1271,7 @@ const fakerGenerator = defineGenerator({
1095
1271
  baseName: meta.file.baseName
1096
1272
  }
1097
1273
  }),
1098
- footer: resolver.resolveFooter(ctx.meta, {
1274
+ footer: resolver.default.footer(ctx.meta, {
1099
1275
  output,
1100
1276
  config,
1101
1277
  file: {
@@ -1119,11 +1295,7 @@ const fakerGenerator = defineGenerator({
1119
1295
  path: dateParser,
1120
1296
  name: dateParser
1121
1297
  }),
1122
- paramEntries.map(({ param, name, typeName }) => renderEntry({
1123
- schema: param.schema,
1124
- name,
1125
- typeName
1126
- })),
1298
+ paramGroups.map((group) => renderEntry(group)),
1127
1299
  responseUnits.map((unit) => renderEntry({
1128
1300
  schema: unit.schema,
1129
1301
  name: unit.name,
@@ -1141,7 +1313,7 @@ const fakerGenerator = defineGenerator({
1141
1313
  renderEntry({
1142
1314
  schema: buildResponseUnionSchema(node, resolver),
1143
1315
  name: responseName,
1144
- typeName: tsResolver.resolveResponseName(node),
1316
+ typeName: tsResolver.response.response(node),
1145
1317
  skipImportNames: responseUnits.map((unit) => unit.name)
1146
1318
  })
1147
1319
  ]
@@ -1159,69 +1331,45 @@ const fakerGenerator = defineGenerator({
1159
1331
  * ```ts
1160
1332
  * import { resolverFaker } from '@kubb/plugin-faker'
1161
1333
  *
1162
- * resolverFaker.default('list pets', 'function') // 'createListPets'
1334
+ * resolverFaker.name('list pets') // 'createListPets'
1163
1335
  * ```
1164
1336
  */
1165
- const resolverFaker = defineResolver(() => {
1166
- return {
1167
- name: "default",
1168
- pluginName: "plugin-faker",
1169
- default(name, type) {
1170
- if (type === "file") return toFilePath(name, (part) => camelCase(part, { prefix: "create" }));
1171
- return ensureValidVarName(camelCase(name, { prefix: "create" }));
1172
- },
1173
- resolveName(name, type) {
1174
- return this.default(name, type);
1175
- },
1176
- resolvePathName(name, type) {
1177
- return this.default(name, type);
1178
- },
1179
- resolveFile({ name, extname, tag, path: groupPath }, context) {
1180
- const inputBaseName = `${context.output.mode === "file" ? "" : this.resolveName(name, "file")}${extname}`;
1181
- const filePath = this.resolvePath({
1182
- baseName: inputBaseName,
1183
- tag,
1184
- path: groupPath
1185
- }, context);
1186
- const baseName = path.basename(filePath);
1187
- return {
1188
- kind: "File",
1189
- id: createHash("sha256").update(filePath).digest("hex"),
1190
- name: path.basename(filePath, extname),
1191
- path: filePath,
1192
- baseName,
1193
- extname,
1194
- meta: { pluginName: this.pluginName },
1195
- sources: [],
1196
- imports: [],
1197
- exports: []
1198
- };
1199
- },
1200
- resolveParamName(node, param) {
1201
- return this.resolveName(`${node.operationId} ${param.in} ${param.name}`);
1202
- },
1203
- resolveDataName(node) {
1204
- return this.resolveName(`${node.operationId} Data`);
1337
+ const resolverFaker = createResolver({
1338
+ pluginName: "plugin-faker",
1339
+ name(name) {
1340
+ return ensureValidVarName(camelCase(name, { prefix: "create" }));
1341
+ },
1342
+ file: { baseName({ name, extname }) {
1343
+ return `${toFilePath(name, (part) => camelCase(part, { prefix: "create" }))}${extname}`;
1344
+ } },
1345
+ param: {
1346
+ name(node, param) {
1347
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
1205
1348
  },
1206
- resolveResponseStatusName(node, statusCode) {
1207
- return this.resolveName(`${node.operationId} Status ${statusCode}`);
1349
+ path(node) {
1350
+ return this.name(`${node.operationId} Path`);
1208
1351
  },
1209
- resolveResponseName(node) {
1210
- return this.resolveName(`${node.operationId} Response`);
1352
+ query(node) {
1353
+ return this.name(`${node.operationId} Query`);
1211
1354
  },
1212
- resolveResponsesName(node) {
1213
- return this.resolveName(`${node.operationId} Responses`);
1355
+ headers(node) {
1356
+ return this.name(`${node.operationId} Headers`);
1357
+ }
1358
+ },
1359
+ response: {
1360
+ status(node, statusCode) {
1361
+ return this.name(`${node.operationId} Status ${statusCode}`);
1214
1362
  },
1215
- resolvePathParamsName(node, param) {
1216
- return this.resolveParamName(node, param);
1363
+ body(node) {
1364
+ return this.name(`${node.operationId} Body`);
1217
1365
  },
1218
- resolveQueryParamsName(node, param) {
1219
- return this.resolveParamName(node, param);
1366
+ response(node) {
1367
+ return this.name(`${node.operationId} Response`);
1220
1368
  },
1221
- resolveHeaderParamsName(node, param) {
1222
- return this.resolveParamName(node, param);
1369
+ responses(node) {
1370
+ return this.name(`${node.operationId} Responses`);
1223
1371
  }
1224
- };
1372
+ }
1225
1373
  });
1226
1374
  //#endregion
1227
1375
  //#region src/plugin.ts
@@ -1277,10 +1425,7 @@ const pluginFaker = definePlugin((options) => {
1277
1425
  regexGenerator,
1278
1426
  printer
1279
1427
  });
1280
- ctx.setResolver(userResolver ? {
1281
- ...resolverFaker,
1282
- ...userResolver
1283
- } : resolverFaker);
1428
+ ctx.setResolver(userResolver ? Resolver.merge(resolverFaker, userResolver) : resolverFaker);
1284
1429
  if (userMacros?.length) ctx.setMacros(userMacros);
1285
1430
  ctx.addGenerator(fakerGenerator);
1286
1431
  } }