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

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 /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(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
  }
@@ -235,14 +593,14 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
235
593
  children: /* @__PURE__ */ jsxs(Function, {
236
594
  export: true,
237
595
  name,
238
- JSDoc: { comments: description ? [`@description ${ast.jsStringEscape(description)}`] : [] },
596
+ JSDoc: { comments: description ? [`@description ${jsStringEscape(description)}`] : [] },
239
597
  params: canOverride ? paramsSignature : void 0,
240
598
  returnType: returnType ?? void 0,
241
599
  children: [seed ? /* @__PURE__ */ jsxs(Fragment, { children: [`faker.seed(${JSON.stringify(seed)})`, /* @__PURE__ */ jsx("br", {})] }) : void 0, `return ${returnExpression}`]
242
600
  })
243
601
  });
244
602
  }
245
- const functionSignature = `${description ? `/**\n * @description ${ast.jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
603
+ const functionSignature = `${description ? `/**\n * @description ${jsStringEscape(description)}\n */\n ` : ""}export function ${name}<TData extends Partial<${typeName}> = object>(data?: TData)`;
246
604
  const seedCode = seed ? `faker.seed(${JSON.stringify(seed)})\n ` : "";
247
605
  const { cyclicSchemas, schemaName } = printer.options;
248
606
  const functionBody = node.type === "object" && !!cyclicSchemas && (node.properties ?? []).some((p) => ast.containsCircularRef(p.schema, {
@@ -271,226 +629,6 @@ function Faker({ node, description, name, typeName, printer, seed, canOverride }
271
629
  });
272
630
  }
273
631
  //#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
632
  //#region ../../internals/shared/src/params.ts
495
633
  const caseParamsCache = /* @__PURE__ */ new WeakMap();
496
634
  /**
@@ -511,6 +649,23 @@ function caseParams(params, casing) {
511
649
  caseParamsCache.set(params, result);
512
650
  return result;
513
651
  }
652
+ /**
653
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
654
+ *
655
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
656
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
657
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
658
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
659
+ */
660
+ function dedupeByCasedName(params) {
661
+ const seen = /* @__PURE__ */ new Set();
662
+ return params.filter((param) => {
663
+ const key = camelCase(param.name);
664
+ if (seen.has(key)) return false;
665
+ seen.add(key);
666
+ return true;
667
+ });
668
+ }
514
669
  //#endregion
515
670
  //#region ../../internals/shared/src/operation.ts
516
671
  /**
@@ -561,6 +716,15 @@ function resolveContentTypeVariants(entries, baseName) {
561
716
  };
562
717
  });
563
718
  }
719
+ function getOperationParameters(node, options = {}) {
720
+ const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
721
+ return {
722
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
723
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
724
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
725
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
726
+ };
727
+ }
564
728
  //#endregion
565
729
  //#region ../../internals/shared/src/group.ts
566
730
  /**
@@ -655,7 +819,7 @@ const fakerKeywordMapper = {
655
819
  return `{...${items.join(", ...")}}`;
656
820
  },
657
821
  matches: (value = "", regexGenerator = "faker") => {
658
- if (regexGenerator === "randexp") return `${ast.toRegExpString(value, "RandExp")}.gen()`;
822
+ if (regexGenerator === "randexp") return `${toRegExpString(value, "RandExp")}.gen()`;
659
823
  return `faker.helpers.fromRegExp("${value}")`;
660
824
  },
661
825
  email: () => "faker.internet.email()",
@@ -666,7 +830,7 @@ function getEnumValues(node) {
666
830
  return node.enumValues ?? [];
667
831
  }
668
832
  function parseEnumValue(value) {
669
- if (typeof value === "string") return ast.stringify(value);
833
+ if (typeof value === "string") return stringify(value);
670
834
  return value;
671
835
  }
672
836
  /**
@@ -734,7 +898,7 @@ const printerFaker = ast.createPrinter((options) => {
734
898
  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
899
  if (!refName) throw new Error("Name not defined for ref node");
736
900
  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;
901
+ const resolvedName = node.ref ? this.options.resolver.name(refName) : refName;
738
902
  if (!this.options.nestedInObject) return `${resolvedName}(data)`;
739
903
  return `${resolvedName}()`;
740
904
  },
@@ -778,7 +942,7 @@ const printerFaker = ast.createPrinter((options) => {
778
942
  },
779
943
  object(node) {
780
944
  const cyclicSchemas = this.options.cyclicSchemas;
781
- const entries = (node.properties ?? []).map((property) => {
945
+ return buildObject((node.properties ?? []).map((property) => {
782
946
  const value = printNested(property.schema, {
783
947
  typeName: this.options.typeName ? indexedTypeName(this.options.typeName, property.name, this.options.nestedInUnion) : void 0,
784
948
  nestedInObject: true
@@ -786,10 +950,9 @@ const printerFaker = ast.createPrinter((options) => {
786
950
  if (cyclicSchemas && ast.containsCircularRef(property.schema, {
787
951
  circularSchemas: cyclicSchemas,
788
952
  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);
953
+ })) 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 }`;
954
+ return `${objectKey(property.name)}: ${value}`;
955
+ }));
793
956
  },
794
957
  ...options.nodes
795
958
  },
@@ -819,22 +982,20 @@ const fakerGenerator = defineGenerator({
819
982
  const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
820
983
  const tsEnumType = pluginTs.options?.enum?.type;
821
984
  const tsEnumTypeSuffix = pluginTs.options?.enum?.typeSuffix ?? "Key";
822
- const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.resolveEnumKeyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.resolveTypeName(schemaName);
985
+ const schemaTypeName = isEnumSchema && tsEnumType === "asConst" ? tsResolver.enum.keyName({ name: schemaName }, tsEnumTypeSuffix) : tsResolver.name(schemaName);
823
986
  const meta = {
824
- name: resolver.resolveName(schemaName),
825
- file: resolver.resolveFile({
987
+ name: resolver.name(schemaName),
988
+ file: resolver.file({
826
989
  name: schemaName,
827
- extname: ".ts"
828
- }, {
990
+ extname: ".ts",
829
991
  root,
830
992
  output,
831
993
  group: group ?? void 0
832
994
  }),
833
995
  typeName: schemaTypeName,
834
- typeFile: tsResolver.resolveFile({
996
+ typeFile: tsResolver.file({
835
997
  name: schemaName,
836
- extname: ".ts"
837
- }, {
998
+ extname: ".ts",
838
999
  root,
839
1000
  output: pluginTs.options?.output ?? output,
840
1001
  group: pluginTs.options?.group ?? void 0
@@ -862,11 +1023,10 @@ const fakerGenerator = defineGenerator({
862
1023
  typeFilePath: meta.typeFile.path
863
1024
  });
864
1025
  const usedImports = filterUsedImports(adapter.getImports(node, (schemaName) => ({
865
- name: resolver.resolveName(schemaName),
866
- path: resolver.resolveFile({
1026
+ name: resolver.name(schemaName),
1027
+ path: resolver.file({
867
1028
  name: schemaName,
868
- extname: ".ts"
869
- }, {
1029
+ extname: ".ts",
870
1030
  root,
871
1031
  output,
872
1032
  group: group ?? void 0
@@ -876,7 +1036,7 @@ const fakerGenerator = defineGenerator({
876
1036
  baseName: meta.file.baseName,
877
1037
  path: meta.file.path,
878
1038
  meta: meta.file.meta,
879
- banner: resolver.resolveBanner(ctx.meta, {
1039
+ banner: resolver.default.banner(ctx.meta, {
880
1040
  output,
881
1041
  config,
882
1042
  file: {
@@ -884,7 +1044,7 @@ const fakerGenerator = defineGenerator({
884
1044
  baseName: meta.file.baseName
885
1045
  }
886
1046
  }),
887
- footer: resolver.resolveFooter(ctx.meta, {
1047
+ footer: resolver.default.footer(ctx.meta, {
888
1048
  output,
889
1049
  config,
890
1050
  file: {
@@ -941,10 +1101,31 @@ const fakerGenerator = defineGenerator({
941
1101
  const pluginTs = ctx.driver.getPlugin(pluginTsName);
942
1102
  if (!pluginTs) return;
943
1103
  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)
1104
+ const params = caseParams(node.parameters, "camelcase");
1105
+ const { path: pathParams, query: queryParams, header: headerParams } = getOperationParameters({
1106
+ ...node,
1107
+ parameters: params
1108
+ }, { paramsCasing: "original" });
1109
+ const paramGroups = [
1110
+ {
1111
+ params: pathParams,
1112
+ name: resolver.param.path,
1113
+ typeName: tsResolver.param.path
1114
+ },
1115
+ {
1116
+ params: queryParams,
1117
+ name: resolver.param.query,
1118
+ typeName: tsResolver.param.query
1119
+ },
1120
+ {
1121
+ params: headerParams,
1122
+ name: resolver.param.headers,
1123
+ typeName: tsResolver.param.headers
1124
+ }
1125
+ ].filter((group) => group.params.length > 0).map((group) => ({
1126
+ schema: buildParams({ params: group.params }),
1127
+ name: group.name(node, group.params[0]),
1128
+ typeName: group.typeName(node, group.params[0])
948
1129
  }));
949
1130
  function expandContentUnits(entries, baseName, tsBaseName, description, decorate) {
950
1131
  const withSchema = entries.filter((entry) => entry.schema);
@@ -981,36 +1162,34 @@ const fakerGenerator = defineGenerator({
981
1162
  skipImportNames: variants.map((variant) => variant.name)
982
1163
  }];
983
1164
  }
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) => ({
1165
+ const responseUnits = node.responses.flatMap((response) => expandContentUnits(response.content ?? [], resolver.response.status(node, response.statusCode), tsResolver.response.status(node, response.statusCode), response.description));
1166
+ const dataUnits = expandContentUnits(node.requestBody?.content ?? [], resolver.response.body(node), tsResolver.response.body(node), node.requestBody?.description, (schema) => ({
986
1167
  ...schema,
987
1168
  description: node.requestBody?.description ?? schema.description
988
1169
  }));
989
- const responseName = resolver.resolveResponseName(node);
1170
+ const responseName = resolver.response.response(node);
990
1171
  const localHelperNames = /* @__PURE__ */ new Set([
991
- ...paramEntries.map((entry) => entry.name),
1172
+ ...paramGroups.map((group) => group.name),
992
1173
  ...responseUnits.map((unit) => unit.name),
993
1174
  ...dataUnits.map((unit) => unit.name),
994
1175
  responseName
995
1176
  ]);
996
1177
  const cyclicSchemas = new Set(ctx.meta.circularNames);
997
1178
  const meta = {
998
- file: resolver.resolveFile({
1179
+ file: resolver.file({
999
1180
  name: node.operationId,
1000
1181
  extname: ".ts",
1001
1182
  tag: node.tags[0] ?? "default",
1002
- path: node.path
1003
- }, {
1183
+ path: node.path,
1004
1184
  root,
1005
1185
  output,
1006
1186
  group: group ?? void 0
1007
1187
  }),
1008
- typeFile: tsResolver.resolveFile({
1188
+ typeFile: tsResolver.file({
1009
1189
  name: node.operationId,
1010
1190
  extname: ".ts",
1011
1191
  tag: node.tags[0] ?? "default",
1012
- path: node.path
1013
- }, {
1192
+ path: node.path,
1014
1193
  root,
1015
1194
  output: pluginTs.options?.output ?? output,
1016
1195
  group: pluginTs.options?.group ?? void 0
@@ -1018,11 +1197,10 @@ const fakerGenerator = defineGenerator({
1018
1197
  };
1019
1198
  function resolveMockImports(schema) {
1020
1199
  return adapter.getImports(schema, (schemaName) => ({
1021
- name: resolver.resolveName(schemaName),
1022
- path: resolver.resolveFile({
1200
+ name: resolver.name(schemaName),
1201
+ path: resolver.file({
1023
1202
  name: schemaName,
1024
- extname: ".ts"
1025
- }, {
1203
+ extname: ".ts",
1026
1204
  root,
1027
1205
  output,
1028
1206
  group: group ?? void 0
@@ -1087,7 +1265,7 @@ const fakerGenerator = defineGenerator({
1087
1265
  baseName: meta.file.baseName,
1088
1266
  path: meta.file.path,
1089
1267
  meta: meta.file.meta,
1090
- banner: resolver.resolveBanner(ctx.meta, {
1268
+ banner: resolver.default.banner(ctx.meta, {
1091
1269
  output,
1092
1270
  config,
1093
1271
  file: {
@@ -1095,7 +1273,7 @@ const fakerGenerator = defineGenerator({
1095
1273
  baseName: meta.file.baseName
1096
1274
  }
1097
1275
  }),
1098
- footer: resolver.resolveFooter(ctx.meta, {
1276
+ footer: resolver.default.footer(ctx.meta, {
1099
1277
  output,
1100
1278
  config,
1101
1279
  file: {
@@ -1119,11 +1297,7 @@ const fakerGenerator = defineGenerator({
1119
1297
  path: dateParser,
1120
1298
  name: dateParser
1121
1299
  }),
1122
- paramEntries.map(({ param, name, typeName }) => renderEntry({
1123
- schema: param.schema,
1124
- name,
1125
- typeName
1126
- })),
1300
+ paramGroups.map((group) => renderEntry(group)),
1127
1301
  responseUnits.map((unit) => renderEntry({
1128
1302
  schema: unit.schema,
1129
1303
  name: unit.name,
@@ -1141,7 +1315,7 @@ const fakerGenerator = defineGenerator({
1141
1315
  renderEntry({
1142
1316
  schema: buildResponseUnionSchema(node, resolver),
1143
1317
  name: responseName,
1144
- typeName: tsResolver.resolveResponseName(node),
1318
+ typeName: tsResolver.response.response(node),
1145
1319
  skipImportNames: responseUnits.map((unit) => unit.name)
1146
1320
  })
1147
1321
  ]
@@ -1159,69 +1333,45 @@ const fakerGenerator = defineGenerator({
1159
1333
  * ```ts
1160
1334
  * import { resolverFaker } from '@kubb/plugin-faker'
1161
1335
  *
1162
- * resolverFaker.default('list pets', 'function') // 'createListPets'
1336
+ * resolverFaker.name('list pets') // 'createListPets'
1163
1337
  * ```
1164
1338
  */
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`);
1339
+ const resolverFaker = createResolver({
1340
+ pluginName: "plugin-faker",
1341
+ name(name) {
1342
+ return ensureValidVarName(camelCase(name, { prefix: "create" }));
1343
+ },
1344
+ file: { baseName({ name, extname }) {
1345
+ return `${toFilePath(name, (part) => camelCase(part, { prefix: "create" }))}${extname}`;
1346
+ } },
1347
+ param: {
1348
+ name(node, param) {
1349
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
1205
1350
  },
1206
- resolveResponseStatusName(node, statusCode) {
1207
- return this.resolveName(`${node.operationId} Status ${statusCode}`);
1351
+ path(node) {
1352
+ return this.name(`${node.operationId} Path`);
1208
1353
  },
1209
- resolveResponseName(node) {
1210
- return this.resolveName(`${node.operationId} Response`);
1354
+ query(node) {
1355
+ return this.name(`${node.operationId} Query`);
1211
1356
  },
1212
- resolveResponsesName(node) {
1213
- return this.resolveName(`${node.operationId} Responses`);
1357
+ headers(node) {
1358
+ return this.name(`${node.operationId} Headers`);
1359
+ }
1360
+ },
1361
+ response: {
1362
+ status(node, statusCode) {
1363
+ return this.name(`${node.operationId} Status ${statusCode}`);
1214
1364
  },
1215
- resolvePathParamsName(node, param) {
1216
- return this.resolveParamName(node, param);
1365
+ body(node) {
1366
+ return this.name(`${node.operationId} Body`);
1217
1367
  },
1218
- resolveQueryParamsName(node, param) {
1219
- return this.resolveParamName(node, param);
1368
+ response(node) {
1369
+ return this.name(`${node.operationId} Response`);
1220
1370
  },
1221
- resolveHeaderParamsName(node, param) {
1222
- return this.resolveParamName(node, param);
1371
+ responses(node) {
1372
+ return this.name(`${node.operationId} Responses`);
1223
1373
  }
1224
- };
1374
+ }
1225
1375
  });
1226
1376
  //#endregion
1227
1377
  //#region src/plugin.ts
@@ -1277,10 +1427,7 @@ const pluginFaker = definePlugin((options) => {
1277
1427
  regexGenerator,
1278
1428
  printer
1279
1429
  });
1280
- ctx.setResolver(userResolver ? {
1281
- ...resolverFaker,
1282
- ...userResolver
1283
- } : resolverFaker);
1430
+ ctx.setResolver(userResolver ? Resolver.merge(resolverFaker, userResolver) : resolverFaker);
1284
1431
  if (userMacros?.length) ctx.setMacros(userMacros);
1285
1432
  ctx.addGenerator(fakerGenerator);
1286
1433
  } }