import { fm } from './FileManage' import * as path from 'path' import * as vscode from 'vscode' import { WINGED_DIRS } from '../consts' import { Dict } from '../type' import * as ts from 'typescript' import { utils } from '../utils' interface DespCache { [key: string]: { content: string desp: Dict } } class TsCompiler { private propsDespCache: DespCache = {} private eventsTypeDespCache: DespCache = {} /** * 根据组件名称获取对应ts文件的路径 * @param compName 组件名称 */ public getTsFilePath(compName: string) { if (fm.validateCompName(compName)) { const targetFileName = fm.getFileName(compName, '.ts') const absDIr = path.join(fm.getRootPath()!, WINGED_DIRS.TS_COMP) return fm.getFilePathByName(targetFileName, absDIr) } return undefined } /** * 提取文档内 export interface xxx {} ---- export class xxx extends xxxV的中间内容 * 预计得到一个函数体,加上剩余内容 例如 : * { name:string,obj:{name:string} } export interface anything { name string} * 这个时候 ts只需要解析函数体,即可获得正确的 props描述 * @param document vscode.TextDoucment * @param compName 组件名称 */ private extractPropsInterface(text: string, compName: string) { const regex = /public propsType!: ([\s\S]*?) public/ const matchRes = text.match(regex) if (matchRes) { const temp = matchRes[1].trim() if (temp[0] === '{') { // 解析 return temp } else { const interfaceName = temp.split('\n')[0].trim() const interfaceFragments = `interface ${interfaceName}` const classFragments = this.getClassFragments(compName) const regex2 = new RegExp(`${interfaceFragments}([\\s\\S]*?)${classFragments}`) const matchRes2 = text.match(regex2) if (matchRes2) { return matchRes2[0].replace(classFragments, '').replace(interfaceFragments, '') } } } return null } /** * 获取类的声明行代码片段 * @param compName 组件名称 */ public getClassFragments(compName: string) { return `export class ${compName} extends ${compName}V<${compName}>` } /** * 解析 props的接口代码片段 生成 propsDescribe * @param text 待解析的interface文本 * @returns 返回props的描述信息(propsDescribe) */ private parsePropsInterface(text: string): Dict | null { const parseNode = (node: ts.Node) => { const _res: Dict = {} node.forEachChild((n) => { if (n.kind === ts.SyntaxKind.PropertySignature) { let key = '' let value: Dict | string = '' n.forEachChild((e) => { if (e.kind === ts.SyntaxKind.Identifier) { key = e.getText() } else if (e.kind === ts.SyntaxKind.QuestionToken) { key += e.getText() } else if (e.kind === ts.SyntaxKind.TypeLiteral) { value = parseNode(e) } else { value = e.getText() } }) _res[key] = value } }) return _res } let res: Dict | null = null try { const sourceFile = ts.createSourceFile('any', `interface x ${text}`, ts.ScriptTarget.ES5, true) sourceFile.forEachChild((item) => { if (item.kind === ts.SyntaxKind.InterfaceDeclaration) { const interfaceNameNode = item.getChildAt(1) if (interfaceNameNode.kind === ts.SyntaxKind.Identifier && interfaceNameNode.getText() === 'x') { res = parseNode(item) } } }) } catch (error) { vscode.window.showErrorMessage('props interface 解析失败', error) } return res } /** * 缓存由 content 解析得到的 desp字典,待content变动,重新解析 content * @param compName 组件名称 * @param content 抽取的interface文本 * @param kind 计算种类 */ private getDespFromCache(compName: string, content: string, kind: 'props' | 'event') { let cache = null if (kind === 'props') { cache = this.propsDespCache } else if (kind === 'event') { cache = this.eventsTypeDespCache } if (cache) { if (!cache[compName]) { cache[compName] = { content: '', desp: {} } } if (cache[compName].content !== content) { cache[compName].content = content if (kind === 'props') { cache[compName].desp = this.parsePropsInterface(content) || {} } else if (kind === 'event') { cache[compName].desp = this.parseEventsType(content) } } return cache[compName].desp } return null } /** * 获取组件的props描述字典 * @param compName 组件名称 */ public async getPropsDespByCompName(compName: string) { const tsFilePath = this.getTsFilePath(compName) if (tsFilePath) { const res = await fm.readFile(tsFilePath) const targetInterface = this.extractPropsInterface(res, compName) if (targetInterface) { return this.getDespFromCache(compName, targetInterface, 'props') } return null } return null } /** * 从类似 '= 0) { let text = '' if (start) { start = false text = document .lineAt(position) .text.substr(0, position.character - (offsetStr ? offsetStr.length : 0)) .trim() } else { text = document.lineAt(i).text.trim() } i-- const closeTagMatch = /(.*?)>$/ if (text.match(closeTagMatch)) { res = { type: 'other', name: '' } break } else { const compName = this.parseTagToCompName(text) if (compName) { res = { type: 'comp', name: compName } break } else { const tagName = this.parseTagToBasic(text) if (tagName) { res = { type: 'tag', name: tagName } break } } } } return res } /** * 获取组件对外暴露的 methods * @param compName 组件名称 */ public async getMethods(compName: string) { const tsFilePath = this.getTsFilePath(compName) if (!fm.validateCompName(compName)) return null if (tsFilePath) { const text = await fm.readFile(tsFilePath) const regex1 = /@vMethod\n(.*)\n/g const regex2 = /public(\s*)(async)?(\s*)(.*)\(/ const effectiveMethods: { name: string; desp: string }[] = [] text.match(regex1)?.forEach((str) => { const res = str.match(regex2) if (res) { effectiveMethods.push({ name: res[4], desp: str }) } }) return effectiveMethods } return null } /** * 提取文档内 export interface xxx {} ---- export class xxx extends xxxV的中间内容 * 预计得到一个函数体,加上剩余内容 例如 : * { name:string,obj:{name:string} } export interface anything { name string} * 这个时候 ts只需要解析函数体,即可获得正确的 props描述 * @param text 文件内容 * @param compName 组件名称 */ private extractEventsType(text: string) { const fileContent = text.replace(/\n/g, '') const eventsTypeFragments = 'public eventsType!:' const regex = new RegExp(`${eventsTypeFragments} {(.*?)}(.*?)public`, 'g') const matchRes = fileContent.match(regex) if (matchRes) { return matchRes[0].replace(eventsTypeFragments, '') } return undefined } /** * 获取组件的eventsType描述字典 * @param compName 组件名称 */ public async getEventsTypeDespByCompName(compName: string) { const tsFilePath = this.getTsFilePath(compName) if (tsFilePath) { const res = await fm.readFile(tsFilePath) const eventsType = this.extractEventsType(res) if (eventsType) { return this.getDespFromCache(compName, eventsType, 'event') } return null } return null } /** * 解析 eventsType 代码片段 生成 describe * 与props接口片段解析逻辑相同,这里不需要递归,只获取第一层 * @param text 待解析的eventsType文本 * @returns 返回 describe */ private parseEventsType(text: string): Dict { const res: Dict = {} const sourceFile = ts.createSourceFile('any', text, ts.ScriptTarget.ES5, true) sourceFile.forEachChild((item) => { if (item.kind === ts.SyntaxKind.Block) { item.forEachChild((node) => { let name = '' let typeNode: ts.Node | null = null if (node.kind === ts.SyntaxKind.LabeledStatement) { name = node.getChildAt(0).getText() typeNode = node.getChildAt(2) } if (typeNode) { res[name] = typeNode.getText() } }) } }) return res } /** * 基于一次打开文件 获取所有相关描述 ,节省分布计算的性能损耗 * @param compName 组件名称 */ public async getAllDesp(compName: string) { const tsFilePath = this.getTsFilePath(compName) const result: { propsType: Dict | null evensType: Dict | null vMethods: { name: string; desp: string }[] | null } = { propsType: null, evensType: null, vMethods: [], } if (tsFilePath) { const res = await fm.readFile(tsFilePath) // propsType const propsInterface = this.extractPropsInterface(res, compName) if (propsInterface) { result.propsType = this.getDespFromCache(compName, propsInterface, 'props') } // eventsType const eventsType = this.extractEventsType(res) if (eventsType) { result.evensType = this.getDespFromCache(compName, eventsType, 'event') } // vMethods result.vMethods = await this.getMethods(compName) return result } } public async getVStateDict(filePath: string) { // 找到 v 文件 逐行取出 const compName = fm.getCompNameByPath(filePath) const compsDir = path.join(fm.getRootPath()!, WINGED_DIRS.V) const targetPath = fm.getFilePathByName(fm.getFileName(compName, '.v.ts'), compsDir) if (targetPath) { const res = await vscode.workspace.openTextDocument(targetPath) let i = 0 let dict = {} let isChangeDict = false while (i <= res.lineCount) { const lineText = res.lineAt(i) if (!lineText.isEmptyOrWhitespace) { const text = lineText.text if (text.includes('@vState')) { const res = this.parseVState(text) if (res) { dict = Object.assign(dict, res) isChangeDict = true } } else if (text.includes('/* refs */')) { break } } i++ } if (isChangeDict) { return dict } } } /** * 解析 vStateFragments * @param text */ private parseVState(vStateFragments: string) { const sourceFile = ts.createSourceFile('any', vStateFragments, ts.ScriptTarget.ES5, true) let vStateMap: Dict | undefined const foo = (node: ts.Node, index?: number) => { // console.log(node.getText(), node.kind) let dict = {} if (node.kind === 75 && index !== 0 && node.getText()) { return node.getText() } if (node.kind === 209) { return node.getText() } if (node.kind === 196) { // node.kind === 196 数组处理 TODO 使支持 Form 补全 return { length: 'number' } } if (node.kind === 193) { node.forEachChild((child) => { const res = foo(child) if (res) { dict = Object.assign(dict, res) } }) return dict } if (node.kind === 210 || node.kind === 281) { let index = 0 const key = node.getChildAt(0).getText() node.forEachChild((child) => { const temp: Dict = {} const res = foo(child, index) if (res) { temp[key] = res dict = Object.assign(dict, temp) } index++ }) return dict } } sourceFile.forEachChild((node) => { if (node.kind === ts.SyntaxKind.ExpressionStatement) { const expressionStatement = node.getChildAt(0) if (expressionStatement) { vStateMap = foo(expressionStatement) } } }) return vStateMap } } export const tc = new TsCompiler()