@kubb/plugin-ts 5.0.0-beta.76 → 5.0.0-beta.79

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.d.ts CHANGED
@@ -126,6 +126,11 @@ type PrinterTsOptions = {
126
126
  * Schema names that represent enums for suffixed key references.
127
127
  */
128
128
  enumSchemaNames?: Set<string>;
129
+ /**
130
+ * Maps a component `$ref` path to its collision-resolved name, so a reference to a renamed
131
+ * component (e.g. `#/components/schemas/Order` → `OrderSchema`) emits the renamed name.
132
+ */
133
+ nameMapping?: ReadonlyMap<string, string>;
129
134
  /**
130
135
  * Custom handler map for node type overrides.
131
136
  */
package/dist/index.js CHANGED
@@ -98,7 +98,7 @@ function toFilePath(name, caseLast = camelCase) {
98
98
  * JavaScript and Java reserved words.
99
99
  * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
100
100
  */
101
- const reservedWords = new Set([
101
+ const reservedWords = /* @__PURE__ */ new Set([
102
102
  "abstract",
103
103
  "arguments",
104
104
  "boolean",
@@ -220,19 +220,19 @@ function ensureValidVarName(name) {
220
220
  /**
221
221
  * `optionalType` values that cause a property's type to include `| undefined`.
222
222
  */
223
- const OPTIONAL_ADDS_UNDEFINED = new Set(["undefined", "questionTokenAndUndefined"]);
223
+ const OPTIONAL_ADDS_UNDEFINED = /* @__PURE__ */ new Set(["undefined", "questionTokenAndUndefined"]);
224
224
  /**
225
225
  * `optionalType` values that render the property key with a `?` token.
226
226
  */
227
- const OPTIONAL_ADDS_QUESTION_TOKEN = new Set(["questionToken", "questionTokenAndUndefined"]);
227
+ const OPTIONAL_ADDS_QUESTION_TOKEN = /* @__PURE__ */ new Set(["questionToken", "questionTokenAndUndefined"]);
228
228
  /**
229
229
  * `enum.type` values that append a `typeSuffix` to the generated enum type alias.
230
230
  */
231
- const ENUM_TYPES_WITH_KEY_SUFFIX = new Set(["asConst"]);
231
+ const ENUM_TYPES_WITH_KEY_SUFFIX = /* @__PURE__ */ new Set(["asConst"]);
232
232
  /**
233
233
  * `enum.type` values that require a runtime value declaration (object, enum, or literal).
234
234
  */
235
- const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
235
+ const ENUM_TYPES_WITH_RUNTIME_VALUE = /* @__PURE__ */ new Set([
236
236
  "enum",
237
237
  "asConst",
238
238
  "constEnum",
@@ -242,7 +242,7 @@ const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set([
242
242
  /**
243
243
  * `enum.type` values whose type declaration is type-only (no runtime value emitted for the type alias).
244
244
  */
245
- const ENUM_TYPES_WITH_TYPE_ONLY = new Set([
245
+ const ENUM_TYPES_WITH_TYPE_ONLY = /* @__PURE__ */ new Set([
246
246
  "asConst",
247
247
  "literal",
248
248
  void 0
@@ -550,7 +550,7 @@ function applyEnumKeyCasing(key, casing = "none") {
550
550
  * type: 'enum',
551
551
  * name: 'petType',
552
552
  * typeName: 'PetType',
553
- * enums: [['cat', 'cat'], ['dog', 'dog']],
553
+ * enums: [['cat', 'cat', 'A cat'], ['dog', 'dog']],
554
554
  * })
555
555
  * ```
556
556
  */
@@ -563,30 +563,39 @@ function createEnumDeclaration({ type = "enum", name, typeName, enums, enumKeyCa
563
563
  if (typeof value === "boolean") return factory.createLiteralTypeNode(value ? factory.createTrue() : factory.createFalse());
564
564
  if (value !== null && value !== void 0) return factory.createLiteralTypeNode(factory.createStringLiteral(value.toString()));
565
565
  }).filter((node) => node !== void 0)))];
566
- if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value]) => {
566
+ if (type === "enum" || type === "constEnum") return [void 0, factory.createEnumDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword), type === "constEnum" ? factory.createToken(ts.SyntaxKind.ConstKeyword) : void 0].filter((modifier) => modifier !== void 0), factory.createIdentifier(typeName), enums.map(([key, value, description]) => {
567
567
  let initializer = factory.createStringLiteral(value?.toString());
568
568
  if (Number.parseInt(value.toString(), 10) === value && isNumber(Number.parseInt(value.toString(), 10))) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
569
569
  else initializer = factory.createNumericLiteral(value);
570
570
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
571
571
  if (isNumber(Number.parseInt(key.toString(), 10))) {
572
572
  const casingKey = applyEnumKeyCasing(`${typeName}_${key}`, enumKeyCasing);
573
- return factory.createEnumMember(propertyName(casingKey), initializer);
573
+ return appendJSDocToNode({
574
+ node: factory.createEnumMember(propertyName(casingKey), initializer),
575
+ comments: [description]
576
+ });
574
577
  }
575
578
  if (key !== null && key !== void 0) {
576
579
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
577
- return factory.createEnumMember(propertyName(casingKey), initializer);
580
+ return appendJSDocToNode({
581
+ node: factory.createEnumMember(propertyName(casingKey), initializer),
582
+ comments: [description]
583
+ });
578
584
  }
579
585
  }).filter((member) => member !== void 0))];
580
586
  const identifierName = name;
581
587
  if (enums.length === 0) return [void 0, factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword))];
582
- return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value]) => {
588
+ return [factory.createVariableStatement([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([factory.createVariableDeclaration(factory.createIdentifier(identifierName), void 0, void 0, factory.createAsExpression(factory.createObjectLiteralExpression(enums.map(([key, value, description]) => {
583
589
  let initializer = factory.createStringLiteral(value?.toString());
584
590
  if (isNumber(value)) if (value < 0) initializer = factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, factory.createNumericLiteral(Math.abs(value)));
585
591
  else initializer = factory.createNumericLiteral(value);
586
592
  if (typeof value === "boolean") initializer = value ? factory.createTrue() : factory.createFalse();
587
593
  if (key !== null && key !== void 0) {
588
594
  const casingKey = applyEnumKeyCasing(key.toString(), enumKeyCasing);
589
- return factory.createPropertyAssignment(propertyName(casingKey), initializer);
595
+ return appendJSDocToNode({
596
+ node: factory.createPropertyAssignment(propertyName(casingKey), initializer),
597
+ comments: [description]
598
+ });
590
599
  }
591
600
  }).filter((property) => property !== void 0), true), factory.createTypeReferenceNode(factory.createIdentifier("const"), void 0)))], ts.NodeFlags.Const)), factory.createTypeAliasDeclaration([factory.createToken(ts.SyntaxKind.ExportKeyword)], factory.createIdentifier(typeName), void 0, factory.createIndexedAccessTypeNode(factory.createParenthesizedType(factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0)), factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, factory.createTypeQueryNode(factory.createIdentifier(identifierName), void 0))))];
592
601
  }
@@ -791,7 +800,7 @@ function buildIndexSignatures(node, propertyCount, print) {
791
800
  * resolve to the same name, which TypeScript merges into a single value+type declaration.
792
801
  */
793
802
  function getEnumNames({ node, enum: enumOptions, resolver }) {
794
- const resolved = resolver.default(node.name, "type");
803
+ const resolved = resolver.resolveTypeName(node.name);
795
804
  return {
796
805
  enumName: enumOptions.constCasing === "pascalCase" ? resolved : camelCase(node.name),
797
806
  typeName: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolved
@@ -817,7 +826,11 @@ function Enum({ node, enum: enumOptions, resolver }) {
817
826
  const [nameNode, typeNode] = createEnumDeclaration({
818
827
  name: enumName,
819
828
  typeName,
820
- enums: node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
829
+ enums: node.namedEnumValues?.map((v) => [
830
+ trimQuotes(v.name.toString()),
831
+ v.value,
832
+ v.description
833
+ ]) ?? node.enumValues?.filter((v) => v !== null && v !== void 0).map((v) => [trimQuotes(v.toString()), v]) ?? [],
821
834
  type: enumOptions.type,
822
835
  enumKeyCasing: enumOptions.keyCasing
823
836
  });
@@ -837,39 +850,6 @@ function Enum({ node, enum: enumOptions, resolver }) {
837
850
  })] });
838
851
  }
839
852
  //#endregion
840
- //#region src/components/Type.tsx
841
- function Type({ name, node, printer, enum: enumOptions, resolver }) {
842
- const enumSchemaNodes = ast.collect(node, { schema(n) {
843
- const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
844
- if (enumNode?.name) return enumNode;
845
- } });
846
- const output = printer.print(node);
847
- if (!output) return;
848
- const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
849
- return {
850
- node,
851
- ...getEnumNames({
852
- node,
853
- enum: enumOptions,
854
- resolver
855
- })
856
- };
857
- });
858
- const shouldExportEnums = enumOptions.type !== "inlineLiteral";
859
- const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
860
- return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
861
- node,
862
- enum: enumOptions,
863
- resolver
864
- }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
865
- name,
866
- isTypeOnly: true,
867
- isExportable: true,
868
- isIndexable: true,
869
- children: output
870
- })] });
871
- }
872
- //#endregion
873
853
  //#region ../../internals/shared/src/params.ts
874
854
  const caseParamsCache = /* @__PURE__ */ new WeakMap();
875
855
  /**
@@ -890,6 +870,23 @@ function caseParams(params, casing) {
890
870
  caseParamsCache.set(params, result);
891
871
  return result;
892
872
  }
873
+ /**
874
+ * Drops parameters that collapse to the same property identity once camelCased, keeping the first.
875
+ *
876
+ * Some specs declare the same parameter twice under different casings (for example AWS S3 lists both
877
+ * `max-uploads` and `MaxUploads`). Both resolve to one output property, so emitting both would yield
878
+ * an object type with a duplicate member, which TypeScript rejects. De-duplicate by the camelCased
879
+ * identity so the resulting group is collision-free regardless of the names each caller carries.
880
+ */
881
+ function dedupeByCasedName(params) {
882
+ const seen = /* @__PURE__ */ new Set();
883
+ return params.filter((param) => {
884
+ const key = camelCase(param.name);
885
+ if (seen.has(key)) return false;
886
+ seen.add(key);
887
+ return true;
888
+ });
889
+ }
893
890
  //#endregion
894
891
  //#region ../../internals/shared/src/operation.ts
895
892
  /**
@@ -943,10 +940,10 @@ function resolveContentTypeVariants(entries, baseName) {
943
940
  function getOperationParameters(node, options = {}) {
944
941
  const params = caseParams(node.parameters, options.paramsCasing === "original" ? void 0 : "camelcase");
945
942
  return {
946
- path: params.filter((param) => param.in === "path"),
947
- query: params.filter((param) => param.in === "query"),
948
- header: params.filter((param) => param.in === "header"),
949
- cookie: params.filter((param) => param.in === "cookie")
943
+ path: dedupeByCasedName(params.filter((param) => param.in === "path")),
944
+ query: dedupeByCasedName(params.filter((param) => param.in === "query")),
945
+ header: dedupeByCasedName(params.filter((param) => param.in === "header")),
946
+ cookie: dedupeByCasedName(params.filter((param) => param.in === "cookie"))
950
947
  };
951
948
  }
952
949
  //#endregion
@@ -983,6 +980,18 @@ function createGroupConfig(group) {
983
980
  //#endregion
984
981
  //#region src/utils.ts
985
982
  /**
983
+ * Tells whether a `const` (single-value enum) should render as a bare literal type (`'active'`)
984
+ * rather than a named enum reference or a runtime enum declaration.
985
+ *
986
+ * The parser folds `const` into a single-value enum node. The adapter decides which schemas are
987
+ * named enums and lists them in `enumSchemaNames`, and references to those names get suffixed (for
988
+ * example `StatusKey`). A const renders as a literal only when the adapter has not registered it as
989
+ * a named enum, which keeps the declaration and its references in sync across adapter versions.
990
+ */
991
+ function isInlineConstEnum(node, enumSchemaNames) {
992
+ return (node.namedEnumValues ?? node.enumValues ?? []).length === 1 && !(node.name && enumSchemaNames?.has(node.name));
993
+ }
994
+ /**
986
995
  * Collects JSDoc annotation strings for a schema node.
987
996
  *
988
997
  * Only uses official JSDoc tags from https://jsdoc.app/: `@description`, `@deprecated`, `@default`, `@example`, `@type`.
@@ -1094,17 +1103,48 @@ function buildData(node, { resolver }) {
1094
1103
  ]
1095
1104
  });
1096
1105
  }
1106
+ /**
1107
+ * The schema a status occupies in the `<Name>Responses` record. A status that documents several
1108
+ * content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
1109
+ * on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
1110
+ * query hooks and `result.data` use.
1111
+ */
1112
+ function buildResponseRecordEntry(node, res, resolver) {
1113
+ const statusName = resolver.resolveResponseStatusName(node, res.statusCode);
1114
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1115
+ if (variants.length <= 1) return ast.factory.createSchema({
1116
+ type: "ref",
1117
+ name: statusName
1118
+ });
1119
+ return ast.factory.createSchema({
1120
+ type: "union",
1121
+ members: resolveContentTypeVariants(variants, statusName).map((variant) => ast.factory.createSchema({
1122
+ type: "object",
1123
+ properties: [ast.factory.createProperty({
1124
+ name: "contentType",
1125
+ required: true,
1126
+ schema: ast.factory.createSchema({
1127
+ type: "enum",
1128
+ enumValues: [variant.contentType]
1129
+ })
1130
+ }), ast.factory.createProperty({
1131
+ name: "data",
1132
+ required: true,
1133
+ schema: ast.factory.createSchema({
1134
+ type: "ref",
1135
+ name: variant.name
1136
+ })
1137
+ })]
1138
+ }))
1139
+ });
1140
+ }
1097
1141
  function buildResponses(node, { resolver }) {
1098
- if (node.responses.length === 0) return null;
1099
1142
  return ast.factory.createSchema({
1100
1143
  type: "object",
1101
1144
  properties: node.responses.map((res) => ast.factory.createProperty({
1102
1145
  name: String(res.statusCode),
1103
1146
  required: true,
1104
- schema: ast.factory.createSchema({
1105
- type: "ref",
1106
- name: resolver.resolveResponseStatusName(node, res.statusCode)
1107
- })
1147
+ schema: buildResponseRecordEntry(node, res, resolver)
1108
1148
  }))
1109
1149
  });
1110
1150
  }
@@ -1120,6 +1160,39 @@ function buildResponseUnion(node, { resolver }) {
1120
1160
  });
1121
1161
  }
1122
1162
  //#endregion
1163
+ //#region src/components/Type.tsx
1164
+ function Type({ name, node, printer, enum: enumOptions, resolver }) {
1165
+ const enumSchemaNodes = ast.collect(node, { schema(n) {
1166
+ const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum);
1167
+ if (enumNode?.name && !isInlineConstEnum(enumNode, printer.options.enumSchemaNames)) return enumNode;
1168
+ } });
1169
+ const output = printer.print(node);
1170
+ if (!output) return;
1171
+ const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
1172
+ return {
1173
+ node,
1174
+ ...getEnumNames({
1175
+ node,
1176
+ enum: enumOptions,
1177
+ resolver
1178
+ })
1179
+ };
1180
+ });
1181
+ const shouldExportEnums = enumOptions.type !== "inlineLiteral";
1182
+ const shouldExportType = enumOptions.type === "inlineLiteral" || enums.every((item) => item.typeName !== name);
1183
+ return /* @__PURE__ */ jsxs(Fragment, { children: [shouldExportEnums && enums.map(({ node }) => /* @__PURE__ */ jsx(Enum, {
1184
+ node,
1185
+ enum: enumOptions,
1186
+ resolver
1187
+ }, node.name)), shouldExportType && /* @__PURE__ */ jsx(File.Source, {
1188
+ name,
1189
+ isTypeOnly: true,
1190
+ isExportable: true,
1191
+ isIndexable: true,
1192
+ children: output
1193
+ })] });
1194
+ }
1195
+ //#endregion
1123
1196
  //#region src/printers/printerTs.ts
1124
1197
  const { isNonNullable } = factory_exports;
1125
1198
  /**
@@ -1175,16 +1248,16 @@ const printerTs = ast.createPrinter((options) => {
1175
1248
  time: dateOrStringNode,
1176
1249
  ref(node) {
1177
1250
  if (!node.name) return null;
1178
- const refName = node.ref ? extractRefName(node.ref) ?? node.name : node.name;
1179
- return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.default(refName, "type") : refName, void 0);
1251
+ const refName = node.ref ? this.options.nameMapping?.get(node.ref) ?? extractRefName(node.ref) ?? node.name : node.name;
1252
+ return createTypeReferenceNode(node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix && this.options.enumSchemaNames?.has(refName) ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enum.typeSuffix) : node.ref ? this.options.resolver.resolveTypeName(refName) : refName, void 0);
1180
1253
  },
1181
1254
  enum(node) {
1182
1255
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? [];
1183
- if (this.options.enum.type === "inlineLiteral" || !node.name) return createUnionDeclaration({
1256
+ if (this.options.enum.type === "inlineLiteral" || !node.name || isInlineConstEnum(node, this.options.enumSchemaNames)) return createUnionDeclaration({
1184
1257
  withParentheses: true,
1185
1258
  nodes: values.filter((v) => v !== null && v !== void 0).map((value) => constToTypeNode(value, typeof value)).filter(isNonNullable)
1186
1259
  }) ?? void 0;
1187
- return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix) : this.options.resolver.default(node.name, "type"), void 0);
1260
+ return createTypeReferenceNode(ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enum.type) && this.options.enum.typeSuffix ? this.options.resolver.resolveEnumKeyName(node, this.options.enum.typeSuffix) : this.options.resolver.resolveTypeName(node.name), void 0);
1188
1261
  },
1189
1262
  union(node) {
1190
1263
  const members = node.members ?? [];
@@ -1310,7 +1383,8 @@ const typeGenerator = defineGenerator({
1310
1383
  group: group ?? void 0
1311
1384
  }).path
1312
1385
  }));
1313
- const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum);
1386
+ const enumNode = ast.narrowSchema(node, ast.schemaTypes.enum);
1387
+ const isEnumSchema = !!enumNode && !isInlineConstEnum(enumNode, enumSchemaNames);
1314
1388
  const meta = {
1315
1389
  name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix) : resolver.resolveTypeName(node.name),
1316
1390
  file: resolver.resolveFile({
@@ -1331,6 +1405,7 @@ const typeGenerator = defineGenerator({
1331
1405
  description: node.description,
1332
1406
  resolver,
1333
1407
  enumSchemaNames,
1408
+ nameMapping: adapter.options.nameMapping,
1334
1409
  nodes: printer?.nodes
1335
1410
  });
1336
1411
  return /* @__PURE__ */ jsxs(File, {
@@ -1413,6 +1488,7 @@ const typeGenerator = defineGenerator({
1413
1488
  keysToOmit,
1414
1489
  resolver,
1415
1490
  enumSchemaNames,
1491
+ nameMapping: adapter.options.nameMapping,
1416
1492
  nodes: printer?.nodes
1417
1493
  });
1418
1494
  return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
@@ -1433,8 +1509,10 @@ const typeGenerator = defineGenerator({
1433
1509
  })] });
1434
1510
  }
1435
1511
  /**
1436
- * Emits an individual type per content type plus a union alias under `baseName`.
1437
- * Shared by the request body and multi-content-type responses.
1512
+ * Emits an individual type per content type plus a plain union alias under `baseName`. Shared by
1513
+ * the request body and multi-content-type responses. The response record discriminates these
1514
+ * variants by content type in {@link buildResponses}; the standalone alias stays a plain union so
1515
+ * query hooks and `result.data` keep the bare body.
1438
1516
  */
1439
1517
  function buildContentTypeVariants(entries, baseName, decorate) {
1440
1518
  const variants = resolveContentTypeVariants(entries, baseName);