85 lines
2.2 KiB
TypeScript
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;
|
|
}
|