import * as ts from "typescript"; import { Declaration, Program, TypeChecker} from "typescript"; import {__approot} from "../config/ApplicationEnvironment"; import {DocEntry} from "../@types/types"; export default class TSDocumentParser{ private fileName:string = ""; private program?:Program; private checker?:TypeChecker; private output?:DocEntry[] = []; public static with(file: string) { const p:TSDocumentParser = new TSDocumentParser(); p.fileName = file; return p; } public parse(){ // Build a program using the set of root file names in fileNames if(!__approot) return; this.program = ts.createProgram([this.fileName], { target: ts.ScriptTarget.Latest, module: ts.ModuleKind.CommonJS }); // Get the checker, we will use it to find more about classes this.checker = this.program.getTypeChecker(); // Visit every sourceFile in the program for (const sourceFile of this.program.getSourceFiles()) { // Walk the tree to search for classes if(sourceFile.fileName.indexOf("node_modules") == -1){ ts.forEachChild(sourceFile, (function(parent:TSDocumentParser){ return (n)=>{ parent.visit(n); } })(this)); } } // print out the doc // fs.writeFileSync("classes.json", JSON.stringify(this.output, undefined, 4)); return this.output; } public getResult(){ return this.output; } /** visit nodes finding exported classes */ public visit(node: ts.Node) { // Only consider exported nodes // if (!this.isNodeExported(node)) { // return; // } if(!node) return; if (node.kind === ts.SyntaxKind.ClassDeclaration) { // This is a top level class, get its symbol const entry:DocEntry | undefined = this.serializeClass((node)); if(entry) this.output?.push(entry); // No need to walk any further, class expressions/inner declarations // cannot be exported } else if (node.kind === ts.SyntaxKind.ModuleDeclaration) { // This is a namespace, visit its children ts.forEachChild(node, (function(parent:TSDocumentParser){ return (n)=>{ parent.visit(n); } })(this)); } } /** Serialize a symbol into a json object */ public serializeSymbol(symbol: ts.Symbol): DocEntry{ let declaration:Declaration|undefined = symbol.valueDeclaration; if (declaration) { const entry:DocEntry = { name: symbol.getName(), documentation: ts.displayPartsToString(symbol.getDocumentationComment(undefined)), type: this.checker?.typeToString(this.checker?.getTypeOfSymbolAtLocation(symbol, declaration)), constructors:[], decorators:[] }; return entry; } return { name: symbol.getName(), documentation: ts.displayPartsToString(symbol.getDocumentationComment(undefined)), constructors:[], decorators:[] }; } /** Serialize a class symbol infomration */ public serializeClass(node: ts.ClassDeclaration):DocEntry|undefined { // let symbol:Symbol|undefined = this.checker?.getSymbolAtLocation(node); let symbol = eval('node.symbol'); if(!symbol) return; let details:DocEntry = this.serializeSymbol(symbol); try{ // Get the construct signatures let declaration:Declaration|undefined = symbol.valueDeclaration; let constructorType:ts.Type|undefined; if (declaration) constructorType = this.checker?.getTypeOfSymbolAtLocation(symbol, declaration); if(details && constructorType) { const dMap = (node.decorators as any)?.map(((parent:TSDocumentParser)=>{ return (n : any)=>{ return parent.serializeDecorator(n); } })(this)); eval('details.decorators = dMap'); const dmap = constructorType.getConstructSignatures().map(((parent:TSDocumentParser)=>{ return (n)=>{ return parent.serializeSignature(n); } })(this)); eval('details.constructors = dmap'); } }catch(e){ console.log(e); } return details; } public serializeDecorator(decorator: ts.Decorator):DocEntry { let fNode:any = decorator.expression.getFirstToken(); let symbol:ts.Symbol|undefined = this.checker?.getSymbolAtLocation(fNode); // let symbol = eval('fNode.symbol'); if (symbol) { let details:DocEntry|undefined = this.serializeSymbol(symbol); let decoratorType:ts.Type|undefined = this.checker?.getTypeOfSymbolAtLocation(symbol, eval('symbol.valueDeclaration')); let entity:DocEntry[]|undefined = details?.constructors; if(details && entity && decoratorType) { const dmap = decoratorType.getCallSignatures().map(((parent:TSDocumentParser)=>{ return (n)=>{ return parent.serializeSignature(n); } })(this)); eval('details.constructors = dmap'); } return details; } return {}; } /** Serialize a signature (call or construct) */ public serializeSignature(signature: ts.Signature):DocEntry { const parametersmap = signature.parameters.map(((parent:TSDocumentParser)=>{ return (n)=>{ return parent.serializeSymbol(n); } })(this)); const ret:DocEntry = { returnType: this.checker?.typeToString(signature.getReturnType()), documentation: ts.displayPartsToString(signature.getDocumentationComment(undefined)) }; eval('ret.parameters = parametersmap'); return ret; } /** True if this is visible outside this file, false otherwise */ public isNodeExported(node: ts.Node): boolean { return (node.flags & ts.NodeFlags.ExportContext) !== 0 || (node.parent && node.parent.kind === ts.SyntaxKind.SourceFile); } }