You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/server/packages/swagger-jsdoc-generator/src/processService.ts

206 lines
4.9 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { SourceFile, SyntaxKind, TypeReferenceNode } from 'ts-morph';
import { getMethodParameters } from './utils';
/**
* 解析并处理 service 文件, 把所有的 Action 都自动加上jsdoc
*/
export async function processService(sourceFile: SourceFile) {
const serviceNameDeclaration = sourceFile
.getDescendantsOfKind(SyntaxKind.GetAccessor)
.find((item) => item.getSymbol().getName() === 'serviceName');
if (!serviceNameDeclaration) {
// 没有定义 serviceName, 不是一个正确的服务,跳过
return;
}
// 目前只先视为一个文件只有一个service不考虑多个
const serviceName = serviceNameDeclaration
.getFirstDescendantByKind(SyntaxKind.ReturnStatement)
.getExpression()
.asKindOrThrow(SyntaxKind.StringLiteral)
.getLiteralText();
console.log('process service:', serviceName);
const actions = findActionMethods(sourceFile);
for (const action of actions) {
const jsdocs = action.getJsDocs();
const len = jsdocs.length;
const lastJsDoc = jsdocs[len - 1]; // 最后一条记录
if (lastJsDoc && lastJsDoc.getText().includes('@swagger')) {
continue;
}
const actionName = action.getSymbol().getEscapedName();
const requestTypeReferenceNode = action
.getFirstChildByKind(SyntaxKind.Parameter)
.getFirstChildByKind(SyntaxKind.TypeReference);
const text = generateOpenapiSchemaText(
serviceName,
actionName,
lastJsDoc?.getCommentText().replaceAll('\n', ' ') ?? '',
getPropertySignatureToSwagger(requestTypeReferenceNode)
);
if (lastJsDoc) {
// 移除以前的注释
lastJsDoc.remove();
}
// 将之前的描述填写到里面
action.addJsDoc(text);
}
await sourceFile.save(); // 将改动保存到原文件中
}
/**
* 获取Action方法列表
*/
function findActionMethods(sourceFile: SourceFile) {
const actions = sourceFile
.getDescendantsOfKind(SyntaxKind.MethodDeclaration)
.filter((item) => {
const parameters = getMethodParameters(item);
if (
parameters.length === 1 &&
['TcPureContext', 'TcContext'].includes(
parameters[0].getType().getSymbol().getEscapedName()
)
) {
return true;
}
return false;
});
return actions;
}
interface SwaggerParamType {
name: string;
type: 'string' | 'integer' | 'string[]' | 'integer[]'; // https://graphql-faas.github.io/OpenAPI-Specification/versions/2.0.html#data-types
}
function generateOpenapiSchemaText(
serviceName: string,
actionName: string,
description: string,
requestParams: SwaggerParamType[],
responseParams?: 'boolean' | SwaggerParamType[]
) {
actionName = actionName.replaceAll('.', '/');
const responseData = responseParams
? responseParams === 'boolean'
? `data:
type: boolean`
: `data:
type: object
properties:
${generateProperties(responseParams, 4)}`
: '';
return `@swagger
/api/${actionName}/${serviceName}:
post:
tags:
- ${actionName}
description: ${description}
requestBody:
content:
application/json:
schema:
type: object
properties:
${generateProperties(requestParams, 14)}
responses:
200:
content:
application/json:
schema:
type: object
properties:
code:
type: integer
example: 200
message:
type: string
example: ok
${paddingWithIndent(responseData, 16)}
`;
}
function generateProperties(
params: SwaggerParamType[],
indent: number
): string {
return paddingWithIndent(
params
.map((p) => {
if (p.type.endsWith('[]')) {
const t = p.type.substring(0, p.type.length - 2);
return `${p.name}:
type: array
items:
type: ${t}`;
} else {
return `${p.name}:
type: ${p.type}`;
}
})
.join('\n'),
indent
);
}
function paddingWithIndent(text: string, indent: number) {
let indentText = '';
Array.from({ length: indent }).forEach(() => {
indentText += ' ';
});
return text.split('\n').join('\n' + indentText);
}
/**
* 将ts的类型转换为swagger可以认识的类型
*/
function getPropertySignatureToSwagger(
typeReferenceNode: TypeReferenceNode
): SwaggerParamType[] {
if (!typeReferenceNode) {
return [];
}
return typeReferenceNode
.getDescendantsOfKind(SyntaxKind.PropertySignature)
.map((item) => {
const name = item.getName();
const type = item.getType();
let typeText: SwaggerParamType['type'] = 'string';
if (type.isArray()) {
typeText = 'string[]';
if (type.isNumber()) {
typeText = 'integer[]';
}
} else {
if (type.isNumber()) {
typeText = 'integer';
}
}
return {
name,
type: typeText,
};
});
}