import ts from "typescript"; import { expandGlob, WalkEntry } from "std/fs/mod.ts"; import { ClassNode, FunctionNode, Node } from "./nodes/mod.ts"; import { TypeRegistry } from "./registry.ts"; /** * Parser to retrieve a friendlier structure of the AST from the typescript compiler API * to work with. * * Reference documentation: https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API#using-the-type-checker */ export class Parser { private readonly _registry = new TypeRegistry(); /** * Parse file matching the given glob pattern. */ public async parse(pattern: string): Promise { const files = await filesMatching(pattern); return this.parseSourceFiles(files); } /** * Parse all source files using the typescript compiler API and build our nodes. */ private parseSourceFiles(files: WalkEntry[]): Node[] { const program = ts.createProgram( files.map((f) => f.path), {} ); const typeChecker = program.getTypeChecker(); return program.getSourceFiles().reduce((nodes, file) => { // That's not interesting for us. if (file.isDeclarationFile) { return nodes; } // file.locals is undocumented but may contain all we need... file.forEachChild((node) => { if (ts.isFunctionDeclaration(node)) { nodes.push( FunctionNode.create( this._registry, node, // @ts-expect-error Wrong getSymbolAtLocation types typeChecker.getSymbolAtLocation(node?.name) ) ); } else if (ts.isClassDeclaration(node)) { nodes.push( ClassNode.create( this._registry, node, // @ts-expect-error Wrong getSymbolAtLocation types typeChecker.getSymbolAtLocation(node?.name) ) ); } }); return nodes; }, []); } } /** * Retrieve all files matching the given glob pattern. */ async function filesMatching(pattern: string): Promise { const files: WalkEntry[] = []; // Let's find all files for await (const entry of expandGlob(pattern)) { if (!entry.isFile) { continue; } files.push(entry); } return files; }