UNPKG

auto-request

Version:

通过Yapi JSON Schema生成接口Axios或Taro接口

1,480 lines (1,464 loc) 529 kB
'use strict'; var fs = require('fs'); var prettier = require('prettier'); var path = require('path'); var parser = require('@babel/parser'); var traverse = require('@babel/traverse'); var jsonSchemaToTypescript = require('json-schema-to-typescript'); var readline = require('readline'); var require$$1 = require('util'); var stream = require('stream'); var require$$3 = require('http'); var require$$4 = require('https'); var require$$0$1 = require('url'); var require$$8 = require('crypto'); var http2 = require('http2'); var require$$4$1 = require('assert'); var require$$0$2 = require('tty'); var zlib = require('zlib'); var events = require('events'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var prettier__namespace = /*#__PURE__*/_interopNamespaceDefault(prettier); var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline); var ErrorType; (function (ErrorType) { ErrorType["PathParams"] = "path-total-error"; ErrorType["SchemaError"] = "schema-error"; ErrorType["PathError"] = "path-error"; ErrorType["Debug"] = "debug"; })(ErrorType || (ErrorType = {})); const ErrorTypeDesc = { [ErrorType.PathParams]: '路径参数数量匹配不上文档', [ErrorType.SchemaError]: '接口参数解析出问题', [ErrorType.PathError]: '路径参数类型没写为path', [ErrorType.Debug]: 'debug数据', }; class Store { constructor() { this.errorUrl = []; this.isTypeScript = true; this.isJsDoc = false; this.errorPath = ''; this.filename = ''; this.logFilename = ''; } pushError(data) { this.errorUrl.push(data); } print() { const errTypeHash = this.errorUrl.reduce((pre, next) => { if (!pre[next.errorType]) { pre[next.errorType] = []; } pre[next.errorType].push({ ...next, errorType: ErrorTypeDesc[next.errorType], }); return pre; }, {}); return JSON.stringify(errTypeHash); } getFileType() { if (this.isTypeScript) { return '.ts'; } return '.js'; } } const store = new Store(); var MethodsType; (function (MethodsType) { MethodsType["Get"] = "get"; MethodsType["Post"] = "post"; MethodsType["Delete"] = "delete"; MethodsType["Put"] = "put"; })(MethodsType || (MethodsType = {})); const initArray = (arrs) => { if (Array.isArray(arrs)) { return arrs; } return []; }; const camelcase = require('camelcase'); const pascalCase = (str) => { return camelcase(str, { pascalCase: true }); }; const clone$1 = require('clone'); const pascalCaseReplaceString = (str, pascalCase = true) => { // 移除路径中的特殊字符,包括冒号(用于路由命名空间如 session:encrypt) return camelcase(str.replace(/[\/|\{|\}|\?|\$|:]/g, ''), { pascalCase }); }; const renderMethodArgs = (args) => { return initArray(args) .filter((item) => item) .join(','); }; /** * 标准化并格式化 URL * 1. 移除路径参数中的正则表达式 {userid:\d+} -> {userid} * 2. 将路径参数转换为模板字符串格式 {userid} -> ${userid} */ const normalizeUrl = (parameters = [], url) => { // 从 parameters 中提取所有可能的路径参数名 const pathParamNames = new Set(); // 优先从 in: "path" 的参数中提取 parameters.forEach(param => { if (param.in === 'path' && param.name) { pathParamNames.add(param.name); } }); // 容错处理:如果 parameters 中有参数名匹配 URL 中的 {xxx} 格式,也认为是路径参数 const urlParamPattern = /\{([^:}]+)(?::[^}]*)?\}/g; let match; const urlCopy = url; // 保存原始URL用于匹配 while ((match = urlParamPattern.exec(urlCopy)) !== null) { const paramName = match[1]; // 检查 parameters 中是否有这个参数名(不限 in 类型) const foundParam = parameters.find(p => p.name === paramName); if (foundParam) { pathParamNames.add(paramName); } } // 替换 URL 中的复杂路径参数为简单格式 // {userid:\d+} -> {userid} // {type:[\w\+=\-]+} -> {type} let normalizedUrl = url; if (pathParamNames.size > 0) { normalizedUrl = url.replace(/\{([^:}]+)(?::[^}]*)?\}/g, (match, paramName) => { // 如果这个参数名在我们的列表中,使用简化格式 if (pathParamNames.has(paramName)) { return `{${paramName}}`; } // 否则保持原样(虽然这种情况应该很少) return match; }); } else { // 如果没有找到参数定义,也尝试简化(容错) normalizedUrl = url.replace(/\{([^:}]+)(?::[^}]*)?\}/g, '{$1}'); } return normalizedUrl; }; const formatUrl = (parameters = [], url) => { // 先标准化 URL const normalized = normalizeUrl(parameters, url); // 然后将路径参数转换为模板字符串格式 const pathParams = initArray(parameters).filter((paramsItem) => paramsItem.in === 'path'); return pathParams.reduce((pre, { name }) => { return pre.replace(`{${name.trim()}}`, `$\{${name.trim()}}`); }, normalized); }; const createMethodsName = (url, method, parameters = []) => { // 使用标准化的 URL 生成方法名 const normalized = normalizeUrl(parameters, url); return pascalCaseReplaceString(`${normalized}${pascalCase(method)}`); }; const formatParamtersItemType = (type) => { if (type === 'integer') { return 'number'; } return 'string'; }; // 获取parameters中path的参数 const renderRenderParametersPaths = (parmeters, isTypeScript = true) => { return initArray(parmeters) .reduce((pre, next) => { if (next.in === 'path') { if (isTypeScript) { pre.push(`${next.name}: ${formatParamtersItemType(next.type)}`); return pre; } pre.push(`${next.name}`); return pre; } return pre; }, []) .join(','); }; const getParameterPath = (parmeters) => { return initArray(parmeters).reduce((pre, next) => { if (next.in === 'path') { pre.push(next); return pre; } return pre; }, []); }; const getParameterData = (parmeters) => { return initArray(parmeters).reduce((pre, next) => { if (next.in === 'body') { pre.push(next); return pre; } return pre; }, []); }; const getParameterParams = (parmeters) => { return initArray(parmeters).reduce((pre, next) => { if (next.in === 'query') { pre.push(next); return pre; } return pre; }, []); }; // 渲染模板头部 const getTemplatePrefix = () => { if (store.isTypeScript) { return ` import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' \n `; } return `import axios from 'axios'\n`; }; // const deepCopy = (json: any) => { // return clone(json); // }; // const formatTsResponse = (name: string) => { // return `${name}Response`; // }; // const createTypeDefineObj = (title: string, properties: any, type: string, required?: any) => { // return { // key: title, // propertiesKey: { // $ref: `#/definitions/${title}`, // }, // definitionsKey: { // title: title, // type: type, // additionalProperties: false, // properties: properties, // required: required, // }, // }; // }; const wapperDefineName = (methName, mode) => { if (mode === 'params') { return `${methName}RequsetParams`; } if (mode === 'data') { return `${methName}RequsetData`; } return `${methName}Response`; }; class Helper { constructor(uri, method, params) { this.getApiInteraces = []; this.renderOptionsStr = { params: '', data: '', }; this.uri = uri; this.method = method; this.sourceSchema = params; this.description = params.description; this.summary = params.summary; } getUrl() { return formatUrl(this.sourceSchema.parameters, this.uri); } getMethodsName() { return createMethodsName(this.uri, this.method, this.sourceSchema.parameters); } getJsDocTypes() { return `{import("./${store.filename}.types.ts").${this.getMethodsName()}}`; } getMethodPrePath() { const getPaths = renderRenderParametersPaths(this.sourceSchema.parameters, store.isTypeScript); this.__vaildateGetMethodPrePath(getParameterPath(this.sourceSchema.parameters)); return getPaths ? `${getPaths}` : ''; } getMethodPreParams() { const getParams = getParameterParams(this.sourceSchema.parameters); if (getParams.length === 0) { return ''; } this.renderOptionsStr.params = 'params'; if (store.isTypeScript) { return `params: P`; } return 'params'; } getJsDocMethodPreParams() { const getParams = getParameterParams(this.sourceSchema.parameters); if (getParams.length === 0) { return ''; } this.renderOptionsStr.params = 'params'; return `params: P`; } getMethodOption() { if (store.isTypeScript) { return `options: AxiosRequestConfig = {}`; } return `options = {}`; } getJsDocMethodOption() { return `options: AxiosRequestConfig`; } // 获取params的ts定义名字 getMethodPreDefineParams() { if (!this.getMethodPreParams()) { return; } return wapperDefineName(this.getMethodsName(), 'params'); } // 校验path参数是否有有问题 __vaildateGetMethodPrePath(getParams) { const matchResult = this.uri.match(/\{\w+\}/g); const getUrlParams = (matchResult ? matchResult : []); const getUrlParamsTotal = getUrlParams.length; const parameters = getParams.length; if (getUrlParamsTotal === 0 && parameters === 0) { return; } // 检查定义的path参数和url上的参数数量是否一致 if (getUrlParamsTotal !== parameters) { store.pushError({ url: this.uri, desc: this.description, method: this.method, errorType: ErrorType.PathParams, }); } // 检测路径参数类型是否匹配 const errMsg = []; getUrlParams.forEach((key) => { const target = getParams.find((item) => String(key).indexOf(item.name) > -1); if (target && target.in !== 'path') { errMsg.push(`${key}的in类型为${target.in}, 应该改为path`); } }); if (errMsg.length > 0) { store.pushError({ url: this.uri, desc: this.description, method: this.method, errMsg: errMsg.join(','), errorType: ErrorType.PathError, }); } } debugPrint(desc, errMsg = '') { if (this.description === desc) { store.pushError({ errorType: ErrorType.Debug, url: this.uri, method: this.method, desc: this.description, errMsg: errMsg, }); } } // 查找方法中所有params参数 getMethodParams() { } // 查找方法中所有data参数 getMethodData() { return getParameterData(this.sourceSchema.parameters); } renderGetMethodData() { if (this.getMethodData().length === 0) { return ''; } this.renderOptionsStr.data = 'data'; if (store.isTypeScript) { return `data: D`; } return `data`; } renderJsDocGetMethodData() { if (this.getMethodData().length === 0) { return ''; } this.renderOptionsStr.data = 'data'; return `data: D`; } // 获取data的ts定义名字 renderGetMethodDefineData() { if (this.method === MethodsType.Get) { return; } if (!this.renderGetMethodData()) { return; } return wapperDefineName(this.getMethodsName(), 'data'); } // 获取接口返回的ts定义名字 getMethodsDefineResponse() { return wapperDefineName(this.getMethodsName(), 'response'); } // 获取返回对象的ts定义名字 renderTsDefineResFeature() { if (!this.sourceSchema.responses['200']) { return []; } return []; } renderTsDefineReqFeature() { const { parameters = [] } = this.sourceSchema; const reqParams = initArray(parameters).filter((params) => params.in === 'body'); if (reqParams.length === 0) { return []; } return []; } // 处理query data的函数 generatorDefineParams() { const querys = initArray(this.sourceSchema.parameters).filter((parameterItem) => parameterItem.in === 'query'); const properties = querys.reduce((pre, next) => { const { name, type, description } = next; pre[name] = { type, description, }; return pre; }, {}); const required = initArray(querys) .filter((item) => item.required) .map((item) => item.name); const key = this.getMethodPreDefineParams(); if (!key) { return { key, propertiesKey: {}, definitionsKey: {}, }; } return { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: false, properties, description: this.description, required: required, }, }; } // 处理response的函数 formatProperties(title, itemSchema) { try { const deepItemSchema = clone$1(itemSchema); // if (!deepItemSchema.properties && deepItemSchema['$ref']) { // const innerTitle = `${title}${pascalCase('items')}`; // return { // [title]:`#/definitions/${title}` // } // } const properties = Object.keys(deepItemSchema.properties).reduce((pre, next) => { const data = deepItemSchema.properties[next]; const innerTitle = `${title}${pascalCase(next)}`; if (['object'].includes(data.type)) { pre[next] = {}; pre[next]['$ref'] = `#/definitions/${innerTitle}`; return pre; } if (['array'].includes(data.type)) { pre[next] = {}; pre[next]['items'] = {}; pre[next]['items']['$ref'] = `#/definitions/${innerTitle}`; return pre; } pre[next] = {}; pre[next] = clone$1(data); return pre; }, {}); return properties; } catch (err) { return {}; } } setTypeObjToInstances(preName, key, itemSchema) { try { if (!key) { // 没有key就是root节点 // 先把这个接口的response根节点写入 if (itemSchema.type === 'object') { const rootTitle = `${preName}`; const rootProperties = this.formatProperties(rootTitle, itemSchema); const apiInstance = { key: rootTitle, propertiesKey: { $ref: `#/definitions/${rootTitle}`, }, definitionsKey: { title: rootTitle, type: itemSchema.type, properties: rootProperties, additionalProperties: false, required: initArray(itemSchema.required), }, }; this.getApiInteraces.push(apiInstance); return; } if (itemSchema.type === 'array') { const rootTitle = `${preName}`; const rootProperties = this.formatProperties(rootTitle, itemSchema.items); const apiInstance = { key: rootTitle, propertiesKey: { items: { $ref: `#/definitions/${rootTitle}`, }, }, definitionsKey: { title: rootTitle, type: itemSchema.type, properties: rootProperties, additionalProperties: false, required: initArray(itemSchema.required), }, }; this.getApiInteraces.push(apiInstance); return; } } // 剩下就是有key的 const rootTitle = `${preName}${pascalCase(key)}`; if (key === 'children') { } if (itemSchema.type === 'array') { const apiInstance = { key: rootTitle, propertiesKey: { items: { $ref: `#/definitions/${rootTitle}`, }, }, definitionsKey: { title: rootTitle, type: itemSchema.items.type, properties: this.formatProperties(rootTitle, itemSchema.items), // TODO, additionalProperties: false, required: initArray(itemSchema.required), }, }; this.getApiInteraces.push(apiInstance); return; } if (itemSchema.type === 'object') { const apiInstance = { key: rootTitle, propertiesKey: { $ref: `#/definitions/${rootTitle}`, }, definitionsKey: { title: rootTitle, type: itemSchema.type, properties: this.formatProperties(rootTitle, itemSchema), // TODO, additionalProperties: false, required: initArray(itemSchema.required), }, }; this.getApiInteraces.push(apiInstance); return; } (() => { const apiInstance = { key: rootTitle, propertiesKey: { $ref: `#/definitions/${rootTitle}`, }, definitionsKey: { title: rootTitle, type: itemSchema.type, additionalProperties: false, required: initArray(itemSchema.required), }, }; this.getApiInteraces.push(apiInstance); })(); } catch (err) { const rootTitle = `${preName}${pascalCase(key)}`; if (itemSchema.type === 'array') { const apiInstance = { key: rootTitle, propertiesKey: { items: { $ref: `#/definitions/${rootTitle}`, }, }, definitionsKey: { title: rootTitle, additionalProperties: true, }, }; this.getApiInteraces.push(apiInstance); } store.pushError({ url: this.uri, method: this.method, errorType: ErrorType.SchemaError, desc: this.description, errMsg: 'setTypeObjToInstances ' + rootTitle + ' ' + err.toString(), }); } } setTypeNotProperties(preName, key, itemSchema) { const rootTitle = `${preName}${pascalCase(key)}`; const apiInstance = { key: rootTitle, propertiesKey: {}, definitionsKey: { title: rootTitle, type: itemSchema.type, additionalProperties: true, required: [], }, }; this.getApiInteraces.push(apiInstance); } setTypeNotProperToArrRef(preName, key, itemSchema) { const rootTitle = `${preName}${pascalCase(key)}`; const apiInstance = { key: rootTitle, propertiesKey: { ...itemSchema.items, }, definitionsKey: { title: rootTitle, type: itemSchema.type, additionalProperties: true, required: [], }, }; this.getApiInteraces.push(apiInstance); } generatorDeepDefine(preName, key, itemSchema) { if (itemSchema.type === 'object') { // 有key说明不是跟response const rootTitle = `${preName}${pascalCase(key)}`; if (key && !itemSchema.properties) { this.setTypeNotProperties(preName, key, itemSchema); return; } if (key && itemSchema.properties) { this.setTypeObjToInstances(preName, key, itemSchema); // TODO } // 深度递归找出里面需要另外设置ref的数据 Object.keys(itemSchema.properties).forEach((itemKey) => { const data = itemSchema.properties[itemKey]; if (['object'].includes(data.type)) { this.generatorDeepDefine(rootTitle, itemKey, clone$1(data)); } else if (['array'].includes(data.type)) { this.generatorDeepDefine(rootTitle, itemKey, clone$1(data)); } }); } if (itemSchema.type === 'array') { this.setTypeObjToInstances(preName, key, itemSchema); if (!itemSchema.items) { return; } if (!key && !itemSchema.items.properties) { this.setTypeNotProperToArrRef(preName, key, itemSchema); return; // 根节点数组的情况 } if (!itemSchema.items.properties) { this.setTypeNotProperties(preName, key, itemSchema); return; } const rootTitle = `${preName}${pascalCase(key)}`; if (itemSchema.items.type === 'object') { Object.keys(clone$1(itemSchema.items.properties)).forEach((itemKey) => { const data = itemSchema.items.properties[itemKey]; if (['object', 'array'].includes(data.type)) { this.generatorDeepDefine(rootTitle, itemKey, clone$1(data)); } }); } else if (itemSchema.items.type === 'array') { const data = itemSchema.items; this.generatorDeepDefine(rootTitle, 'item', clone$1(data)); } else ; } } // 从 example 字符串中解析出数据结构 parseExampleToSchema(example) { try { const parsed = JSON.parse(example); return this.inferSchemaFromValue(parsed); } catch (err) { // 如果解析失败,返回一个空的 object schema return { type: 'object', properties: {}, additionalProperties: true, }; } } // 从实际值推断 schema 结构(所有字段都设为 any 类型,非必填) inferSchemaFromValue(value) { if (value === null || value === undefined) { return { type: 'any' }; } if (Array.isArray(value)) { const itemSchema = value.length > 0 ? this.inferSchemaFromValue(value[0]) : { type: 'any' }; return { type: 'array', items: itemSchema, }; } if (typeof value === 'object') { const properties = {}; Object.keys(value).forEach(key => { // 所有字段都设置为 any 类型 properties[key] = { type: 'any', description: '', }; }); return { type: 'object', properties, additionalProperties: true, required: [], // 所有字段都是非必填 }; } // 基础类型也返回 any return { type: 'any' }; } generatorDefineData() { return { key: '', propertiesKey: {}, definitionsKey: {}, }; } generatorDefineResponse() { const key = this.getMethodsDefineResponse(); const response = this.sourceSchema.responses['200']; if (!response) { // 没有200响应,记录错误并生成一个 any 类型的默认定义 store.pushError({ url: this.uri, method: this.method, errorType: ErrorType.SchemaError, desc: this.description, errMsg: '缺少 response 200 定义', }); const defaultAnyInfo = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: true, description: this.description, }, }; this.getApiInteraces.push(defaultAnyInfo); return this.getApiInteraces; } const resSchema = response.schema; if (!resSchema) { const emptyInfof = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: true, description: this.description, }, }; this.getApiInteraces.push(emptyInfof); return this.getApiInteraces; } // 检查是否只有 example 而没有 properties(边界情况) const hasProperties = resSchema.properties || (resSchema.items && resSchema.items.properties); const hasExample = resSchema.example; if (!hasProperties && hasExample && typeof hasExample === 'string') { // 只有 example,没有结构化的 properties // 尝试从 example 中解析出结构 const parsedSchema = this.parseExampleToSchema(hasExample); // 生成一个简单的定义,所有字段都是 any 类型 const exampleInfo = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: parsedSchema.type || 'object', properties: parsedSchema.properties || {}, additionalProperties: true, required: [], description: this.description, }, }; this.getApiInteraces.push(exampleInfo); return this.getApiInteraces; } // 先把根节点的response设置好 this.setTypeObjToInstances(key, '', resSchema); // 编辑response对象 if (resSchema.type === 'array') { this.generatorDeepDefine(key, '', resSchema.items); } else { this.generatorDeepDefine(key, '', resSchema); } return this.getApiInteraces; } } // import { MethodsType, GetSchema } from '@/define'; // 生成ts的schema const handleRenderApiTsFileFeature = (apis) => { const init = { title: 'Api', type: 'object', properties: {}, definitions: {}, additionalProperties: false, preDefine: '', }; return apis.reduce((pre, next) => { try { [next.generatorDefineParams()].forEach((item) => { const { propertiesKey, definitionsKey, key } = item; if (!key) { return; } pre['properties'][key] = propertiesKey; pre['definitions'][key] = definitionsKey; }); // get是没有data的 post的情况下 存在实例全局对象里面在下面的函数一起处理 next.generatorDefineData(); next.generatorDefineResponse().forEach((item) => { const { propertiesKey, definitionsKey, key } = item; if (!key) { return; } pre['properties'][key] = propertiesKey; pre['definitions'][key] = definitionsKey; }); } catch (err) { next.debugPrint(next.description, '【handleRenderApiTsFileFeature】' + err.toString()); } return pre; }, init); }; // 返回接口定义名字的import列表 const wrapperPreFixDefineName = (helpers) => { return helpers.reduce((pre, next) => { const params = next.getMethodPreDefineParams(); if (params) { pre.push(params); } const data = next.renderGetMethodDefineData(); if (data) { pre.push(data); } const response = next.getMethodsDefineResponse(); if (response) { pre.push(response); } return pre; }, []); }; // 返回接口泛型参数 const wrapperMethodPreInterface = (helpers) => { const p = []; const params = helpers.getMethodPreDefineParams(); if (params) { p.push(`P extends ${params}`); } const data = helpers.renderGetMethodDefineData(); if (data) { p.push(`D extends ${data}`); } const response = helpers.getMethodsDefineResponse(); if (response) { p.push(`S = AxiosResponse < ${response} > `); } return p; }; const wrapperPreImportDefine = (defines) => { return ` import { \n${defines}\n } from './${store.filename}.define'; `; }; /** * 渲染 TypeScript 或 JavaScript 接口函数 */ const renderMethodTemplate = (instance, includeData = false) => { const pathArgs = instance.getMethodPrePath(); const paramsArgs = instance.getMethodPreParams(); const dataArgs = includeData ? instance.renderGetMethodData() : ''; const optionArgs = instance.getMethodOption(); const args = renderMethodArgs([pathArgs, paramsArgs, dataArgs, optionArgs]); const requestArgs = renderMethodArgs([ `url: \`${instance.getUrl()}\``, `method: \'${instance.method}\'`, `${instance.renderOptionsStr.data}`, `${instance.renderOptionsStr.params}`, `...options`, ]); const preInterface = renderMethodArgs(wrapperMethodPreInterface(instance)); const summary = instance.description === instance.summary ? '' : `\n * @summary ${instance.summary}`; // TypeScript 模式 if (store.isTypeScript) { return ` /*** * @description ${instance.description}${summary} **/ export const ${instance.getMethodsName()} = <${preInterface}>(${args}):Promise<S> => { return axios.request({${requestArgs}}) }\n `; } // JsDoc 模式 if (store.isJsDoc) { return ` /*** * @type {import("./${store.filename}.types.ts").${instance.getMethodsName()}} * @description ${instance.description}${summary} **/ export const ${instance.getMethodsName()} = (${args}) => { return axios.request({${requestArgs}}) }\n `; } // 纯 JavaScript 模式 return ` /*** * @description ${instance.description}${summary} **/ export const ${instance.getMethodsName()} = (${args}) => { return axios.request({${requestArgs}}) }\n `; }; /** * 渲染 JsDoc 类型定义 */ const renderJsDocTemplate = (instance, includeData = false) => { const pathArgs = instance.getMethodPrePath(); const paramsArgs = instance.getJsDocMethodPreParams(); const dataArgs = includeData ? instance.renderJsDocGetMethodData() : ''; const optionArgs = instance.getJsDocMethodOption(); const args = renderMethodArgs([pathArgs, paramsArgs, dataArgs, optionArgs]); const preInterface = renderMethodArgs(wrapperMethodPreInterface(instance)); const response = 'S'; return ` export type ${instance.getMethodsName()} = <${preInterface}>(${args})=> Promise<${response}>\n `; }; /** * 创建方法渲染器 * @param uri - 接口路径 * @param method - 请求方法 * @param params - 接口参数定义 * @param includeData - 是否包含 data 参数(POST/PUT/DELETE 需要) */ const createMethodRenderer = (uri, method, params, includeData = false) => { const instance = new Helper(uri, method, params); // 渲染模板方法,可以通过传入自定义的方法覆盖 const renderTemplate = (renderCallback) => { if (typeof renderCallback === 'function') { return renderCallback.call(instance); } return renderMethodTemplate(instance, includeData); }; // 渲染 JsDoc 模板 const renderJsDocMethod = (renderJsDocCallback) => { if (typeof renderJsDocCallback === 'function') { return renderJsDocCallback.call(instance); } return renderJsDocTemplate(instance, includeData); }; return { instance, renderTemplate, renderJsDocTemplate: renderJsDocMethod, }; }; class GetHelper extends Helper { // 待处理的数据 constructor(uri, method, params) { super(uri, method, params); } // get是没有data的 generatorDefineData() { return { key: '', propertiesKey: {}, definitionsKey: {}, }; } } const useGetMethdos = (uri, method, params, _response) => { // GET 请求不包含 data 参数 return createMethodRenderer(uri, method, params, false); }; class PostHelper extends Helper { // 待处理的数据 constructor(uri, method, params) { super(uri, method, params); } // post/put/delete 有 data 参数 generatorDefineData() { const key = this.renderGetMethodDefineData(); if (!key) { return { key: '', propertiesKey: {}, definitionsKey: {}, }; } const bodySchema = this.sourceSchema.parameters.find((item) => item.in === 'body'); if (!bodySchema) { return { key: '', propertiesKey: {}, definitionsKey: {}, }; } // 边界情况1:bodySchema 没有 schema 定义 if (!bodySchema.schema) { store.pushError({ url: this.uri, method: this.method, errorType: ErrorType.SchemaError, desc: this.description, errMsg: '缺少 body schema 定义', }); const defaultAnyInfo = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: true, description: this.description, }, }; this.getApiInteraces.push(defaultAnyInfo); return { key: '', propertiesKey: {}, definitionsKey: {}, }; } // 边界情况2:schema 只有 type,没有 properties(object 类型) const schema = bodySchema.schema; const hasProperties = schema.properties || (schema.items && schema.items.properties); if (schema.type === 'object' && !hasProperties) { store.pushError({ url: this.uri, method: this.method, errorType: ErrorType.SchemaError, desc: this.description, errMsg: 'body schema 缺少 properties 定义', }); const defaultAnyInfo = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: true, description: this.description, }, }; this.getApiInteraces.push(defaultAnyInfo); return { key: '', propertiesKey: {}, definitionsKey: {}, }; } // 正常处理流程 try { // 先把根节点的response设置好 if (bodySchema.schema?.type !== 'array') { this.setTypeObjToInstances(key, '', bodySchema.schema); } // 编辑response对象 this.generatorDeepDefine(key, '', bodySchema.schema); } catch (err) { // 如果处理过程中出错,生成 any 类型定义 store.pushError({ url: this.uri, method: this.method, errorType: ErrorType.SchemaError, desc: this.description, errMsg: `body schema 处理失败: ${err.message}`, }); const defaultAnyInfo = { key, propertiesKey: { $ref: `#/definitions/${key}`, }, definitionsKey: { title: key, type: 'object', additionalProperties: true, description: this.description, }, }; this.getApiInteraces.push(defaultAnyInfo); } // post的情况会在处理response的时候顺带处理 return { key: '', propertiesKey: {}, definitionsKey: {}, }; } } const usePostMethdos = (uri, method, params) => { // POST/PUT/DELETE 请求包含 data 参数 return createMethodRenderer(uri, method, params, true); }; /** * 路径处理器 - 优化重复的遍历逻辑 */ const clone = require('clone'); // 全局忽略 URL 列表 let globalIgnoreUrls = []; /** * 设置全局忽略 URL 列表 */ function setIgnoreUrls(ignoreUrls = []) { globalIgnoreUrls = ignoreUrls; } /** * 检查 URL 是否应该被忽略 */ function shouldIgnoreUrl(url) { return globalIgnoreUrls.includes(url); } /** * 遍历所有路径和方法 * @param paths - Swagger 的 paths 对象 * @param callback - 处理每个路径方法的回调函数 */ const forEachPath = (paths, callback) => { Object.keys(paths).forEach((uri) => { // 检查是否在忽略名单中 if (shouldIgnoreUrl(uri)) { console.log(`⚠️ 跳过忽略的 URL: ${uri}`); return; } const pathItem = paths[uri]; Object.keys(pathItem).forEach((method) => { callback({ uri, method, schema: clone(pathItem[method]) }); }); }); }; /** * 生成所有路径的 Helper 实例 * @param paths - Swagger 的 paths 对象 * @returns Helper 实例数组 */ const generateHelpers = (paths) => { const helpers = []; forEachPath(paths, ({ uri, method, schema }) => { if (method === MethodsType.Get) { helpers.push(new GetHelper(uri, method, schema)); } else { helpers.push(new PostHelper(uri, method, schema)); } }); return helpers; }; /** * 生成所有路径的模板渲染器 * @param paths - Swagger 的 paths 对象 * @param renderCallback - 自定义渲染回调函数 * @returns 渲染后的模板字符串数组 */ const generateTemplates = (paths, renderCallback) => { const templates = []; forEachPath(paths, ({ uri, method, schema }) => { const schemaData = schema; const renderer = method === 'get' ? useGetMethdos(uri, method, schema, schemaData['responses']) : usePostMethdos(uri, method, schema); templates.push(renderer.renderTemplate(renderCallback)); }); return templates; }; /** * 生成所有路径的 JsDoc 模板 * @param paths - Swagger 的 paths 对象 * @param renderJsDocCallback - 自定义 JsDoc 渲染回调函数 * @returns 渲染后的 JsDoc 字符串数组 */ const generateJsDocTemplates = (paths, renderJsDocCallback) => { const templates = []; forEachPath(paths, ({ uri, method, schema }) => { const schemaData = schema; const renderer = method === 'get' ? useGetMethdos(uri, method, schema, schemaData['responses']) : usePostMethdos(uri, method, schema); templates.push(renderer.renderJsDocTemplate(renderJsDocCallback)); }); return templates; }; /** * 生成路径定义对象 * @param paths - Swagger paths 对象 * @returns 定义对象 */ const generatePathsDefine = (paths) => { return handleRenderApiTsFileFeature(generateHelpers(paths)); }; /** * 生成路径定义名称列表 * @param paths - Swagger paths 对象 * @returns 定义名称数组 */ const generatePathsDefineNames = (paths) => { const helpers = []; forEachPath(paths, ({ uri, method, schema }) => { helpers.push(new Helper(uri, method, schema)); }); return wrapperPreFixDefineName(helpers); }; /** * 从 JavaScript 代码字符串中提取导出的函数名及其注释 * @param code - JavaScript 代码字符串 * @returns 包含函数名和注释的对象数组 */ const extractFunctionsFromCode = (code) => { try { // 解析为 AST(支持 ES6+ 语法) const ast = parser.parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'], // 支持 JSX 和 TypeScript attachComment: true, // 启用注释解析 }); const exportedFunctions = []; // 遍历 AST traverse(ast, { ExportNamedDeclaration(path) { // 提取函数上方的注释 const comments = path.node.leadingComments || []; const description = extractCommentValue(comments, 'description'); const summary = extractCommentValue(comments, 'summary'); // 处理导出的函数声明 if (path.node.declaration?.type === 'FunctionDeclaration') { const functionName = path.node.declaration.id?.name; if (functionName) { exportedFunctions.push({ name: functionName, description, summary }); } } // 处理导出的变量声明(函数表达式或箭头函数) if (path.node.declaration?.type === 'VariableDeclaration') { path.node.declaration.declarations.forEach((declaration) => { if (declaration.init?.type === 'FunctionExpression' || declaration.init?.type === 'ArrowFunctionExpression') { const functionName = declaration.id?.name; if (functionName) { exportedFunctions.push({ name: functionName, description, summary }); } } }); } }, }); return exportedFunctions; } catch (err) { console.error('解析代码失败:', err.message); return []; } }; /** * 从注释中提取特定标签的值 * @param comments - 注释节点数组 * @param tag - 标签名(如 'description' 或 'summary') * @returns 标签对应的值 */ const extractCommentValue = (comments, tag) => { for (const comment of comments) { if (comment.type === 'CommentBlock') { const lines = comment.value.split('\n'); for (const line of lines) { const match = line.match(new RegExp(`@${tag}\\s+(.*)`)); if (match) { return match[1].trim(); } } } } return undefined; }; /** * 从文件中提取导出的函数名 * @param filePath - 文件路径 * @returns 导出的函数名数组 */ const extractFunctionsFromFile = (filePath) => { try { const code = fs__namespace.readFileSync(filePath, 'utf-8'); return extractFunctionsFromCode(code); } catch (err) { console.error(`读取文件失败: ${path__namespace.basename(filePath)}`, err.message); return []; } }; /** * 比较两个函数数组,输出新增和删除的函数 * @param oldFunctions - 旧的函数数组 * @param newFunctions - 新的函数数组 * @returns 包含新增和删除函数信息的对象 */ const compareFunctions = (oldFunctions, newFunctions) => { // 提取函数名数组 const oldFunctionNames = oldFunctions.map((func) => func.name); const newFunctionNames = newFunctions.map((func) => func.name); // 找出新增的函数 const added = newFunctions.filter((func) => !oldFunctionNames.includes(func.name)); // 找出删除的函数 const removed = oldFunctions.filter((func) => !newFunctionNames.includes(func.name)); return { added, removed }; }; /** * 生成多列 Markdown 表格 * @param data - 表格数据(二维数组) * @param headers - 表头数组 * @param title - 表格标题(可选) * @returns Markdown 表格字符串 */ const generateMultiColumnMarkdownTable = (data, headers, title) => { if (data.length === 0) { return title ? `**${title}**\n\n无数据` : '无数据'; } // 生成表头 let table = `| ${headers.join(' | ')} |\n`; table += `| ${headers.map(() => '---').join(' | ')} |\n`; // 生成表格内容 data.forEach((row) => { table += `| ${row.map((cell) => cell || '').join(' | ')} |\n`; }); // 添加标题(如果提供) if (title) { table = `**${title}**\n\n${table}`; } return table; }; class Diff { diff(oldStr, newStr, // Type below is not accurate/complete - see above for full possibilities - but it compiles options = {}) { let callback; if (typeof options === 'function') { callback = options; options = {}; } else if ('callback' in options) { callback = options.callback; } // Allow subclasses to massage the input prior to running const oldString = this.castInput(oldStr, options); const newString = this.castInput(newStr, options); const oldTokens = this.removeEmpty(this.tokenize(oldString, options)); const newTokens = this.removeEmpty(this.tokenize(newString, options)); return this.diffWithOptionsObj(oldTokens, newTokens, options, callback); } diffWithOptionsObj(oldTokens, newTokens, options, callback) { var _a; const done = (value) => { value = this.postProcess(value, options); if (callback) { setTimeout(function () { callback(value); }, 0); return undefined; } else { return value; } }; const newLen = newTokens.length, oldLen = oldTokens.length; let editLength = 1; let maxEditLength = newLen + oldLen; if (options.maxEditLength != null) { maxEditLength = Math.min(maxEditLength, options.maxEditLength); } const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity; const abortAfterTimestamp = Date.now() + maxExecutionTime; const bestPath = [{ oldPos: -1, lastComponent: undefined }]; // Seed editLength = 0, i.e. the content starts with the same values let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options); if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) { // Identity per the equality and tokenizer return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens)); } // Once we hit the right edge of the edit graph on some diagonal k, we can // definitely reach the end of the edit graph in no more than k edits, so // there's no point in considering any moves to diagonal k+1 any more (from // which we're guaranteed to need at least k+1 more edits). // Similarly, once we've reached the bottom of the edit graph, there's no // point considering moves to lower diagonals. // We record this fact by setting minDiagonalToConsider and // maxDiagonalToConsider to some finite value once we've hit the edge of // the edit graph. // This optimization is not faithful to the original algorithm presented in // Myers's paper, which instead pointlessly extends D-paths off the end of // the edit graph - see page 7 of Myers's paper which notes this point // explicitly and illustrates it with a diagram. This has major performance // implications for some common scenarios. For instance, to compute a diff // where the new text simply appends d characters on the end of the // original text of length n, the true Myers algorithm will take O(n+d^2) // time while this optimization needs only O(n+d) time. let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity; // Main worker method. checks all permutations of a given edit length for acceptance. const execEditLength = () => { for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) { let basePath; const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1]; if (removePath) { // No one else is going to attempt to use this value, clear it // @ts-expect-error - perf optimisation. This type-violating value will never be read. bestPath[diagonalPath - 1] = undefined; } let canAdd = false; if (addPath) {