From 44e96a86ea51e809a725565fb98fde1bb6ab5845 Mon Sep 17 00:00:00 2001 From: Cano1997 <1978141412@qq.com> Date: Wed, 23 Apr 2025 20:28:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E4=B8=8E=E7=8E=AF=E5=A2=83=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=94=9F=E6=88=90cli?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config-generator/config-generator.ts | 205 +++++++++++++++++ .../environments-generator.ts | 216 ++++++++++++++++++ .../generator-factory/generator-factory.ts | 6 + 3 files changed, 427 insertions(+) create mode 100644 packages/core/src/generate-doc/generator/config-generator/config-generator.ts create mode 100644 packages/core/src/generate-doc/generator/environments-generator/environments-generator.ts diff --git a/packages/core/src/generate-doc/generator/config-generator/config-generator.ts b/packages/core/src/generate-doc/generator/config-generator/config-generator.ts new file mode 100644 index 0000000..d7315ed --- /dev/null +++ b/packages/core/src/generate-doc/generator/config-generator/config-generator.ts @@ -0,0 +1,205 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import path from 'path'; +import ts from 'typescript'; +import fs from 'fs'; +import { GeneratorBase } from '../generator-base/generator-base'; +import { findImportedFilePath, parseField, parseTS, uniqueData } from '../util'; + +/** + * @description 全局参数文档生成器 + * @export + * @class ConfigGenerator + * @extends {GeneratorBase} + */ +export class ConfigGenerator extends GeneratorBase { + /** + * @description 解析文件内容 + * @returns {*} {Promise} + * @memberof ConfigGenerator + */ + async parse(): Promise { + // 初始化文档对象 + this.docObject = { + caption: '', + description: '', + props: [], + interfaces: [], + }; + // 解析各个文件 + const { fieldFilePath } = this.config.item; + this.parseFieldFile(fieldFilePath, this.docObject); + this.docObject.props = uniqueData(this.docObject.props); + } + + /** + * @description 解析字段文件 + * @param {string} fieldFilePath + * @param {Record} docObject + * @memberof ConfigGenerator + */ + parseFieldFile(fieldFilePath: string, docObject: Record): void { + const filePath = path.resolve( + this.config.dirPathConfig.runtimeBasePath, + fieldFilePath, + ); + const sourceFile = parseTS(filePath); + const visit = (node: any) => { + if (ts.isInterfaceDeclaration(node)) { + const jsDocs = ts.getJSDocCommentsAndTags(node); + // 提取接口的comment作为标题 + const interfaceComment = jsDocs.length > 0 ? jsDocs[0].comment : '-'; + docObject.caption = interfaceComment; + // 提取接口的@description标记作为描述 + const descriptionTag = jsDocs.find( + (doc: any) => + doc.tags && + doc.tags.some((tag: any) => tag.tagName.text === 'description'), + ); + const description = descriptionTag + ? (descriptionTag as any).tags.find( + (tag: any) => tag.tagName.text === 'description', + ).comment + : '-'; + docObject.description = description; + node.members.forEach((member: any) => { + const parameterType = member.type?.getText(sourceFile); + if (parameterType) { + const item = parseField(member, sourceFile); + const isInterface = this.processType( + sourceFile, + filePath, + parameterType, + item.name, + ); + if (!isInterface && ts.isPropertySignature(member)) { + docObject.props.push(item); + } + } + }); + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + } + + /** + * @description 生成文件内容 + * @returns {*} {Promise} + * @memberof ConfigGenerator + */ + async generate(): Promise { + // 生成Markdown内容 + let markdownContent = ''; + + // 标题 + markdownContent += `# ${this.docObject.caption}\n\n`; + + // 描述 + markdownContent += `${this.docObject.description}\n\n`; + + // 输入参数 + markdownContent += `## 应用配置\n\n`; + // 输入参数表格 + if ( + this.docObject && + this.docObject.props && + this.docObject.props.length > 0 + ) { + markdownContent += `| 名称 | 说明 | 类型 | 默认值 |\n`; + markdownContent += `|----------|------|------|------|\n`; + this.docObject.props.forEach((prop: any) => { + markdownContent += + `| ${prop.name} | ${prop.description} | ` + + `${prop.parameterType}` + + ` | ${prop.defaultvalue} |\n`; + }); + markdownContent += `\n\n`; + } else { + markdownContent += `暂无内容\n\n`; + } + + this.docObject.interfaces.forEach((item: any) => { + // 标题 + markdownContent += `## ${item.caption}\n\n`; + + // 输入参数表格 + if (item && item.props && item.props.length > 0) { + markdownContent += `| 名称 | 说明 | 类型 | 默认值 |\n`; + markdownContent += `|----------|------|------|------|\n`; + item.props.forEach((prop: any) => { + markdownContent += + `| ${prop.name} | ${prop.description} | ` + + `${prop.parameterType}` + + ` | ${prop.defaultvalue} |\n`; + }); + markdownContent += `\n\n`; + } else { + markdownContent += `暂无内容\n\n`; + } + }); + + return markdownContent; + } + + /** + * @description 处理参数类型 + * @param {ts.SourceFile} fileAst + * @param {string} tempFilePath + * @param {string} parameterType + * @memberof ConfigGenerator + */ + processType( + fileAst: ts.SourceFile, + tempFilePath: string, + parameterType: string, + fieldName: string, + ): boolean { + const importPath = findImportedFilePath(fileAst, parameterType); + if (importPath) { + const targetFilePath = path.resolve( + path.dirname(tempFilePath), + importPath, + ); + const targetFileContent = fs.readFileSync(`${targetFilePath}.ts`, 'utf8'); + const targetSourceFileAst = ts.createSourceFile( + path.basename(targetFilePath), + targetFileContent, + ts.ScriptTarget.Latest, + true, + ); + + ts.forEachChild(targetSourceFileAst, targetNode => { + if (ts.isInterfaceDeclaration(targetNode)) { + const result: any = { + props: [], + }; + const jsDocs = ts.getJSDocCommentsAndTags(targetNode); + // 提取接口的@description标记作为描述 + const descriptionTag = jsDocs.find( + (doc: any) => + doc.tags && + doc.tags.some((tag: any) => tag.tagName.text === 'description'), + ); + const description = descriptionTag + ? (descriptionTag as any).tags.find( + (tag: any) => tag.tagName.text === 'description', + ).comment + : '-'; + result.caption = `${description}(${fieldName})`; + targetNode.members.forEach(member => { + if (ts.isPropertySignature(member)) { + result.props.push(parseField(member, targetSourceFileAst)); + } + }); + this.docObject.interfaces.push(result); + } + }); + return true; + } + return false; + } +} diff --git a/packages/core/src/generate-doc/generator/environments-generator/environments-generator.ts b/packages/core/src/generate-doc/generator/environments-generator/environments-generator.ts new file mode 100644 index 0000000..37e77c6 --- /dev/null +++ b/packages/core/src/generate-doc/generator/environments-generator/environments-generator.ts @@ -0,0 +1,216 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import path from 'path'; +import ts from 'typescript'; +import fs from 'fs'; +import { GeneratorBase } from '../generator-base/generator-base'; +import { findImportedFilePath, parseField, parseTS, uniqueData } from '../util'; + +/** + * @description 环境参数文档生成器 + * @export + * @class EnvironmentGenerator + * @extends {GeneratorBase} + */ +export class EnvironmentGenerator extends GeneratorBase { + /** + * @description 解析文件内容 + * @returns {*} {Promise} + * @memberof EnvironmentGenerator + */ + async parse(): Promise { + // 初始化文档对象 + this.docObject = { + caption: '', + description: '', + props: [], + interfaces: [], + enums: [], + }; + // 解析各个文件 + const { fieldFilePath } = this.config.item; + this.parseFieldFile(fieldFilePath, this.docObject); + this.docObject.props = uniqueData(this.docObject.props); + } + + /** + * @description 解析字段文件 + * @param {string} fieldFilePath + * @param {Record} docObject + * @memberof EnvironmentGenerator + */ + parseFieldFile(fieldFilePath: string, docObject: Record): void { + const filePath = path.resolve( + this.config.dirPathConfig.coreBasePath, + fieldFilePath, + ); + const sourceFile = parseTS(filePath); + const visit = (node: any) => { + if (ts.isInterfaceDeclaration(node)) { + const jsDocs = ts.getJSDocCommentsAndTags(node); + // 提取接口的comment作为标题 + const interfaceComment = jsDocs.length > 0 ? jsDocs[0].comment : '-'; + docObject.caption = interfaceComment; + // 提取接口的@description标记作为描述 + const descriptionTag = jsDocs.find( + (doc: any) => + doc.tags && + doc.tags.some((tag: any) => tag.tagName.text === 'description'), + ); + const description = descriptionTag + ? (descriptionTag as any).tags.find( + (tag: any) => tag.tagName.text === 'description', + ).comment + : '-'; + docObject.description = description; + node.members.forEach((member: any) => { + if (ts.isPropertySignature(member)) { + docObject.props.push(parseField(member, sourceFile)); + } + const parameterType = member.type?.getText(sourceFile); + if (parameterType) { + this.processType(sourceFile, filePath, parameterType); + } + }); + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + } + + /** + * @description 生成文件内容 + * @returns {*} {Promise} + * @memberof EnvironmentGenerator + */ + async generate(): Promise { + // 生成Markdown内容 + let markdownContent = ''; + + // 标题 + markdownContent += `# ${this.docObject.caption}\n\n`; + + // 描述 + markdownContent += `${this.docObject.description}\n\n`; + + // 输入参数 + markdownContent += `## 介绍\n\n`; + // 输入参数表格 + if ( + this.docObject && + this.docObject.props && + this.docObject.props.length > 0 + ) { + markdownContent += `| 名称 | 说明 | 类型 | 默认值 |\n`; + markdownContent += `|----------|------|------|------|\n`; + this.docObject.props.forEach((prop: any) => { + const item = this.docObject.enums.find( + (x: any) => x.caption === prop.parameterType, + ); + let parameterType = prop.parameterType; + if (item) { + parameterType = item.props.map((x: any) => x.name).join(' | '); + } + markdownContent += + `| ${prop.name} | ${prop.description} | ` + + `${parameterType}` + + ` | ${prop.defaultvalue} |\n`; + }); + markdownContent += `\n\n`; + } + + this.docObject.interfaces.forEach((item: any) => { + // 输入参数表格 + if (item && item.props && item.props.length > 0) { + // 标题 + markdownContent += `## ${item.caption}\n\n`; + markdownContent += `| 名称 | 说明 | 类型 | 默认值 |\n`; + markdownContent += `|----------|------|------|------|\n`; + item.props.forEach((prop: any) => { + markdownContent += + `| ${prop.name} | ${prop.description} | ` + + `${prop.parameterType}` + + ` | ${prop.defaultvalue} |\n`; + }); + markdownContent += `\n\n`; + } + }); + + return markdownContent; + } + + /** + * @description 处理参数类型 + * @param {ts.SourceFile} fileAst + * @param {string} tempFilePath + * @param {string} parameterType + * @memberof EnvironmentGenerator + */ + processType( + fileAst: ts.SourceFile, + tempFilePath: string, + parameterType: string, + ): void { + const importPath = findImportedFilePath(fileAst, parameterType); + if (importPath && importPath.includes('.')) { + const targetFilePath = path.resolve( + path.dirname(tempFilePath), + importPath, + ); + const targetFileContent = fs.readFileSync(`${targetFilePath}.ts`, 'utf8'); + const targetSourceFileAst = ts.createSourceFile( + path.basename(targetFilePath), + targetFileContent, + ts.ScriptTarget.Latest, + true, + ); + + ts.forEachChild(targetSourceFileAst, targetNode => { + if (ts.isInterfaceDeclaration(targetNode)) { + const result: any = { + props: [], + }; + const fieldName = targetNode.name.text || '-'; + const jsDocs = ts.getJSDocCommentsAndTags(targetNode); + const descriptionTag = jsDocs.find( + (doc: any) => + doc.tags && + doc.tags.some((tag: any) => tag.tagName.text === 'description'), + ); + const description = descriptionTag + ? (descriptionTag as any).tags.find( + (tag: any) => tag.tagName.text === 'description', + ).comment + : '-'; + result.caption = `${description}(${fieldName})`; + targetNode.members.forEach(member => { + if (ts.isPropertySignature(member)) { + result.props.push(parseField(member, targetSourceFileAst)); + } + }); + this.docObject.interfaces.push(result); + } + if (ts.isEnumDeclaration(targetNode)) { + const result: any = { + props: [], + }; + const fieldName = targetNode.name.text || '-'; + result.caption = fieldName; + targetNode.members.forEach(member => { + const memberJsDocs = ts.getJSDocCommentsAndTags(member); + const description = + memberJsDocs.length > 0 ? memberJsDocs[0].comment : '-'; + result.props.push({ + name: member.name.getText(), + description, + }); + }); + this.docObject.enums.push(result); + } + }); + } + } +} diff --git a/packages/core/src/generate-doc/generator/generator-factory/generator-factory.ts b/packages/core/src/generate-doc/generator/generator-factory/generator-factory.ts index 4fc3248..95d32e4 100644 --- a/packages/core/src/generate-doc/generator/generator-factory/generator-factory.ts +++ b/packages/core/src/generate-doc/generator/generator-factory/generator-factory.ts @@ -1,7 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { IGenerator, IGeneratorConfig } from '../../interface'; +import { ConfigGenerator } from '../config-generator/config-generator'; import { CtrlGenerator } from '../ctrl-generator/ctrl-generator'; import { EditorGenerator } from '../editor-generator/editor-generator'; +import { EnvironmentGenerator } from '../environments-generator/environments-generator'; import { ViewGenerator } from '../view-generator/view-generator'; export class GeneratorFactory { @@ -23,6 +25,10 @@ export class GeneratorFactory { return new EditorGenerator(config); case 'VIEW': return new ViewGenerator(config); + case 'CONFIG': + return new ConfigGenerator(config); + case 'ENVIRONMENTS': + return new EnvironmentGenerator(config); default: throw new Error(`不支持的生成器类型: ${type}`); } -- Gitee