vite-bolt/plugin/ts2json.ts

214 lines
5.5 KiB
TypeScript
Raw Normal View History

2023-02-01 17:52:53 +08:00
import ts from "typescript";
export interface DocsType {
name?: string;
type?: string;
description?: string;
default?: string;
required?: boolean;
version?: number | string;
}
export interface Interfaces {
name?: {
description?: string;
/** 是否不渲染 markdown */
ignore?: string;
props?: DocsType[];
};
}
/**
* Ts Interface json
*/
export default class TsToJson {
checker: any;
interfaces: any;
fileNames: any;
types: any;
_createProgram(fileNames: string[], options: ts.CompilerOptions) {
this.interfaces = {};
this.types = {};
// 使用filename中的根文件名构建一个程序;
const program = ts.createProgram(fileNames, options);
// 获取检查器,我们将使用它来查找更多关于 ast 的信息
this.checker = program.getTypeChecker();
this.fileNames = fileNames;
// 访问程序中的每个sourceFile
for (const sourceFile of program.getSourceFiles()) {
if (!sourceFile.isDeclarationFile) {
// 遍历树以搜索 interface 和 type
ts.forEachChild(sourceFile, (node) => this._visitAst(node));
}
}
}
/**
* 访
* @param node
* @returns
*/
_visitAst(node: ts.Node) {
// 只考虑导出的节点
if (!this._isNodeExported(node)) {
return;
}
switch (ts.SyntaxKind[node.kind]) {
case "InterfaceDeclaration":
this.generateInterfaceDeclaration(node);
break;
case "TypeAliasDeclaration":
this.generateTypeAliasDeclaration(node);
break;
default:
break;
}
}
/**
* Interface json
* @param node
*/
generateInterfaceDeclaration(node: ts.Node) {
if (
// @ts-ignore
node.parent?.fileName.replace(/\\/g, "/") !==
this.fileNames[0].replace(/\\/g, "/")
)
return;
const newNode = node as ts.InterfaceDeclaration;
const symbol = this.checker.getSymbolAtLocation(newNode.name);
const { escapedName } = symbol;
const { name = escapedName as string, ...rest } = this._getDocs(symbol);
this.interfaces[name] = {
...rest,
props: [],
};
if (newNode.heritageClauses) {
// 是否有extends
const firstHeritageClause = newNode.heritageClauses[0];
const firstHeritageClauseType = firstHeritageClause.types![0];
const extendsType = this.checker.getTypeAtLocation(
firstHeritageClauseType.expression
);
if (extendsType?.symbol?.members && extendsType.symbol.members.size > 0) {
// 接口 extends 接口Interface
extendsType.symbol.members.forEach((member: any) => {
this.interfaces[name].props.push(this._serializeSymbol(member));
});
} else if (firstHeritageClauseType) {
console.log("暂时不支持接口继承Type类型");
// 接口 extends 类型Type
// const type = this.checker.typeToTypeNode(
// this.checker.getTypeAtLocation(firstHeritageClauseType),
// firstHeritageClauseType,
// ts.NodeBuilderFlags.InTypeAlias
// // ts.TypeFormatFlags.InTypeAlias
// );
// console.log(firstHeritageClauseType.getFullText());
}
}
symbol.members.forEach((member: any) => {
this.interfaces[name].props.push(this._serializeSymbol(member));
});
}
/**
* Type json
* @param node
*/
generateTypeAliasDeclaration(node: ts.Node) {
const type = this.checker.typeToString(
this.checker.getTypeAtLocation(node),
node,
ts.TypeFormatFlags.InTypeAlias
);
const name = (node as any).name.escapedText;
this.types[name] = type;
}
/**
* symbol序列化为json对象
* @param symbol
* @returns
*/
_serializeSymbol(symbol: ts.Symbol): DocsType | undefined {
if (!symbol) return;
const docs = this._getDocs(symbol);
const questionToken = (symbol as any).valueDeclaration?.questionToken;
const type = this.checker.typeToString(
this.checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration)
);
let newType = type;
if (type.indexOf("|") > 0) {
newType = type
.split("|")
.map((item: any) => {
const hasType = this.types[item.trim()];
if (hasType) {
return ` ${hasType} `;
}
return item;
})
.join("|");
} else if (this.types[type]) {
newType = this.types[type];
}
return {
name: symbol.getName(),
required: !questionToken,
type: newType,
...docs,
};
}
/**
* jsDoc信息
* @param symbol
* @returns
*/
_getDocs(symbol: ts.Symbol): DocsType {
const docs: DocsType = {};
if (
symbol.getJsDocTags &&
Array.isArray(symbol.getJsDocTags(this.checker))
) {
symbol.getJsDocTags(this.checker).forEach((tag) => {
(docs as any)[tag.name] = tag.text && tag.text[0].text;
});
}
return docs;
}
/**
* Truefalse
* @param node
* @returns boolean
*/
_isNodeExported(node: ts.Node): boolean {
return (
(ts.getCombinedModifierFlags(node as ts.Declaration) &
ts.ModifierFlags.Export) !==
0 ||
(!!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile)
);
}
parse(
fileNames: string,
options: ts.CompilerOptions = {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJS,
}
) {
this._createProgram([fileNames], options);
return this.interfaces;
}
}