2023-03-08 12:26:10 +01:00

85 lines
2.2 KiB
TypeScript

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<Node[]> {
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<Node[]>((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<WalkEntry[]> {
const files: WalkEntry[] = [];
// Let's find all files
for await (const entry of expandGlob(pattern)) {
if (!entry.isFile) {
continue;
}
files.push(entry);
}
return files;
}