Skip to content

Commit 4a80f61

Browse files
fix: fix duplicate type reference error #601, support desc line break #306 (#622)
* fix: fix duplicate type reference error #601 * perf: support desc line break #306 * chore: clean code
1 parent 9f2f643 commit 4a80f61

File tree

32 files changed

+2894
-1763
lines changed

32 files changed

+2894
-1763
lines changed

.changeset/gentle-cobras-drum.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openapi-ts-request': patch
3+
---
4+
5+
perf: support desc line break #306

.changeset/green-chicken-glow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openapi-ts-request': patch
3+
---
4+
5+
fix: fix duplicate type reference error #601

src/generator/serviceGenarator.ts

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ export default class ServiceGenerator {
106106
protected interfaceTPConfigs: ITypeItem[] = [];
107107
// 记录每个类型被哪些模块使用(用于拆分类型文件)
108108
protected typeModuleMap: Map<string, Set<string>> = new Map();
109+
// 从原始 schema key 到最终类型名称的映射(用于处理重名情况)
110+
protected schemaKeyToTypeNameMap: Map<string, string> = new Map();
109111

110112
constructor(config: GenerateServiceProps, openAPIData: OpenAPIObject) {
111113
this.config = {
@@ -268,10 +270,16 @@ export default class ServiceGenerator {
268270
const reactQueryMode = this.config.reactQueryMode;
269271
const reactQueryFileName = displayReactQueryFileName(reactQueryMode);
270272

273+
// 先处理重复的 typeName,建立类型名称映射(在生成 service controller 之前)
274+
this.interfaceTPConfigs = this.getInterfaceTPConfigs();
275+
this.schemaKeyToTypeNameMap = handleDuplicateTypeNames(
276+
this.interfaceTPConfigs
277+
);
278+
271279
if (!isOnlyGenTypeScriptType) {
272280
const prettierError = [];
273281

274-
// 生成 service controller 文件
282+
// 生成 service controller 文件(此时类型名称映射已经建立)
275283
this.getServiceTPConfigs().forEach((tp) => {
276284
const { list, ...restTp } = tp;
277285
const payload: Omit<typeof tp, 'list'> &
@@ -335,10 +343,6 @@ export default class ServiceGenerator {
335343
}
336344
}
337345

338-
// 处理重复的 typeName
339-
this.interfaceTPConfigs = this.getInterfaceTPConfigs();
340-
handleDuplicateTypeNames(this.interfaceTPConfigs);
341-
342346
// 生成 ts 类型声明
343347
if (!isGenJavaScript) {
344348
if (this.config.isSplitTypesByModule) {
@@ -414,7 +418,10 @@ export default class ServiceGenerator {
414418
}
415419
);
416420
} else {
417-
// 传统方式:所有类型在一个文件
421+
// 在生成文件前,对 interfaceTPConfigs 进行排序(因为 getServiceTPConfigs 可能添加了新类型)
422+
this.interfaceTPConfigs.sort((a, b) =>
423+
a.typeName.localeCompare(b.typeName)
424+
);
418425
this.genFileFromTemplate(
419426
`${interfaceFileName}.ts`,
420427
TypescriptFileType.interface,
@@ -745,6 +752,7 @@ export default class ServiceGenerator {
745752
: '',
746753
enumLabelType: isEnum ? (result.enumLabelType as string) : '',
747754
description: result.description as string,
755+
originalSchemaKey: schemaKey, // 保存原始 schema key,用于处理重名后的类型映射
748756
});
749757

750758
// 记录 schema 类型归属
@@ -771,7 +779,8 @@ export default class ServiceGenerator {
771779
}
772780
});
773781

774-
return lastTypes?.sort((a, b) => a.typeName.localeCompare(b.typeName)); // typeName排序
782+
return lastTypes;
783+
// return lastTypes?.sort((a, b) => a.typeName.localeCompare(b.typeName)); // typeName排序
775784
}
776785

777786
private getServiceTPConfigs() {
@@ -963,32 +972,46 @@ export default class ServiceGenerator {
963972
};
964973

965974
return {
975+
// 如果 functionName 和 summary 相同,则不显示 summary
976+
...(() => {
977+
const rawDesc =
978+
functionName === newApi.summary
979+
? newApi.description || ''
980+
: [
981+
newApi.summary,
982+
newApi.description,
983+
(newApi.responses?.default as ResponseObject)
984+
?.description
985+
? `返回值: ${(newApi.responses?.default as ResponseObject).description}`
986+
: '',
987+
]
988+
.filter((s) => s)
989+
.join(' ');
990+
const hasLineBreak = lineBreakReg.test(rawDesc);
991+
// 格式化描述文本,让描述支持换行
992+
const desc = hasLineBreak
993+
? '\n * ' + rawDesc.split('\n').join('\n * ') + '\n *'
994+
: rawDesc;
995+
// 如果描述有换行,pathInComment 结尾加换行使 */ 单独一行
996+
const pathInComment = hasLineBreak
997+
? formattedPath.replace(/\*/g, '&#42;') + '\n'
998+
: formattedPath.replace(/\*/g, '&#42;');
999+
const originApifoxRunLink = newApi?.['x-run-in-apifox'];
1000+
const apifoxRunLink =
1001+
hasLineBreak && originApifoxRunLink
1002+
? ' * ' + originApifoxRunLink + '\n'
1003+
: originApifoxRunLink;
1004+
return { desc, pathInComment, apifoxRunLink };
1005+
})(),
9661006
...newApi,
9671007
functionName: this.config.isCamelCase
9681008
? camelCase(functionName)
9691009
: functionName,
9701010
typeName: this.getFunctionParamsTypeName(newApi),
9711011
path: getPrefixPath(),
972-
pathInComment: formattedPath.replace(/\*/g, '&#42;'),
973-
apifoxRunLink: newApi?.['x-run-in-apifox'],
9741012
hasPathVariables: formattedPath.includes('{'),
9751013
hasApiPrefix: !!this.config.apiPrefix,
9761014
method: newApi.method,
977-
// 如果 functionName 和 summary 相同,则不显示 summary
978-
desc:
979-
functionName === newApi.summary
980-
? (newApi.description || '').replace(lineBreakReg, '')
981-
: [
982-
newApi.summary,
983-
newApi.description,
984-
(newApi.responses?.default as ResponseObject)
985-
?.description
986-
? `返回值: ${(newApi.responses?.default as ResponseObject).description}`
987-
: '',
988-
]
989-
.filter((s) => s)
990-
.join(' ')
991-
.replace(lineBreakReg, ''),
9921015
hasHeader: !!params?.header || !!body?.mediaType,
9931016
params: finalParams,
9941017
hasParams: Boolean(keys(finalParams).length),
@@ -1196,21 +1219,34 @@ export default class ServiceGenerator {
11961219
private getType(schemaObject: ISchemaObject, namespace?: string) {
11971220
const customTypeHookFunc = this.config.hook?.customType;
11981221
const schemas = this.openAPIData.components?.schemas;
1222+
const schemaKeyToTypeNameMap = this.schemaKeyToTypeNameMap;
11991223

12001224
if (customTypeHookFunc) {
1225+
// 为自定义 hook 提供支持映射的 originGetType
1226+
const originGetTypeWithMapping = (
1227+
schema: ISchemaObject,
1228+
ns?: string,
1229+
s?: typeof schemas
1230+
) => getDefaultType(schema, ns, s, schemaKeyToTypeNameMap);
1231+
12011232
const type = customTypeHookFunc({
12021233
schemaObject,
12031234
namespace,
12041235
schemas,
1205-
originGetType: getDefaultType,
1236+
originGetType: originGetTypeWithMapping,
12061237
});
12071238

12081239
if (typeof type === 'string') {
12091240
return type;
12101241
}
12111242
}
12121243

1213-
return getDefaultType(schemaObject, namespace, schemas);
1244+
return getDefaultType(
1245+
schemaObject,
1246+
namespace,
1247+
schemas,
1248+
schemaKeyToTypeNameMap
1249+
);
12141250
}
12151251

12161252
private getFunctionParamsTypeName(data: APIDataType) {
@@ -1684,6 +1720,15 @@ export default class ServiceGenerator {
16841720
// 处理公共类型的依赖:如果公共类型依赖某个模块类型,将该类型也移到公共类型
16851721
Helper.moveCommonTypeDependenciesToCommon({ moduleTypes, commonTypes });
16861722

1723+
// 对每个模块的类型列表进行排序
1724+
moduleTypes.forEach((types) => {
1725+
types.sort((a, b) => a.typeName.localeCompare(b.typeName));
1726+
});
1727+
1728+
// 对公共类型和枚举类型进行排序
1729+
commonTypes.sort((a, b) => a.typeName.localeCompare(b.typeName));
1730+
enumTypes.sort((a, b) => a.typeName.localeCompare(b.typeName));
1731+
16871732
return { moduleTypes, commonTypes, enumTypes };
16881733
}
16891734
}

src/generator/serviceGeneratorHelper.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export function resolveEnumObject(params: {
116116
const getActualType = (type: typeof schemaObject.type): string => {
117117
if (Array.isArray(type)) {
118118
// 如果是数组,返回第一个非 null 类型
119-
return type.find((t) => t !== 'null') || 'string';
119+
return (type.find((t) => t !== 'null') as string | undefined) || 'string';
120120
}
121121
return type;
122122
};
@@ -187,11 +187,17 @@ export function resolveEnumObject(params: {
187187
}
188188
}
189189

190+
// 格式化描述文本,让描述支持换行
191+
const formattedDescription =
192+
schemaObject.description && lineBreakReg.test(schemaObject.description)
193+
? '\n * ' + schemaObject.description.split('\n').join('\n * ') + '\n'
194+
: schemaObject.description;
195+
190196
return {
191197
isEnum: true,
192198
type: Array.isArray(enumArray) ? enumStr : 'string',
193199
enumLabelType: enumLabelTypeStr,
194-
description: schemaObject.description,
200+
description: formattedDescription,
195201
};
196202
}
197203

@@ -241,8 +247,11 @@ export function getProps(params: {
241247
// 获取描述信息,如果是 $ref 引用,尝试获取引用对象的描述
242248
let desc = [schema.title, schema.description]
243249
.filter((item) => item)
244-
.join(' ')
245-
.replace(lineBreakReg, '');
250+
.join(' ');
251+
// 格式化描述文本,让描述支持换行
252+
desc = lineBreakReg.test(desc)
253+
? '\n * ' + desc.split('\n').join('\n * ') + '\n'
254+
: desc;
246255

247256
// 如果是 $ref 引用,尝试获取引用对象的描述
248257
if (isReferenceObject(schema) && openAPIData) {
@@ -251,10 +260,13 @@ export function getProps(params: {
251260
refName
252261
] as SchemaObject;
253262
if (refSchema) {
254-
const refDesc = [refSchema.title, refSchema.description]
263+
let refDesc = [refSchema.title, refSchema.description]
255264
.filter((item) => item)
256-
.join(' ')
257-
.replace(lineBreakReg, '');
265+
.join(' ');
266+
// 格式化描述文本,让描述支持换行
267+
refDesc = lineBreakReg.test(refDesc)
268+
? '\n * ' + refDesc.split('\n').join('\n * ') + '\n'
269+
: refDesc;
258270
if (refDesc) {
259271
desc = desc + refDesc;
260272
}
@@ -348,7 +360,9 @@ export function resolveRefObject<T>(params: {
348360
// 处理 OpenAPI 3.1 的 type 数组情况
349361
if (Array.isArray(schemaObj.type)) {
350362
// 如果是数组,使用第一个非 null 类型
351-
resolvedType = schemaObj.type.find((t) => t !== 'null') || 'string';
363+
resolvedType =
364+
(schemaObj.type.find((t) => t !== 'null') as string | undefined) ||
365+
'string';
352366
} else {
353367
resolvedType = schemaObj.type;
354368
}
@@ -420,7 +434,7 @@ export function getResponsesType(params: {
420434

421435
// 生成带注释的类型定义
422436
return formattedDescription
423-
? ` /**\n * ${formattedDescription}\n */\n ${statusCode}: ${lastType};`
437+
? ` /**\n * ${formattedDescription}\n */\n ${statusCode}: ${lastType};`
424438
: ` ${statusCode}: ${lastType};`;
425439
}
426440
);

src/generator/type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export interface ITypeItem {
3838
description?: string;
3939
/** 类型所属的模块名称(用于拆分类型文件) */
4040
belongsToModules?: Set<string>;
41+
/** 原始的 schema key(用于处理重名后的类型映射) */
42+
originalSchemaKey?: string;
4143
}
4244

4345
export type ICustomSchemaObject = SchemaObject & { isAllowed?: boolean };

0 commit comments

Comments
 (0)