Our compiler is nothing but a thin layer over the TypeScript Compiler. We use vfs, the same one they use in their own Playground.

The Initialisation:

First we need to fetch some .d.ts files and make them part of our TypeScript program. Without them, the building blocks like Math won’t work.

https://app.metz.sh/play/1bcc2050f0164dc3ab621f933514a85a?minimal=true

Parsing:

The first step is to identify all the relevant keywords and their metadata.

We parse the entirety of given files, and visit each class declaration we encounter. If found, we then visit each method in those declarations. At each visit, we try to figure out associated metadata, like is the class hidden, is the method private. And whatever of interest we encounter, we store in an array. By the end of our visit, we get all the relevant keywords.

Next is to understand the call graph. And the only nodes we are interested in are the keywords we parsed earlier! Because anything else is just noise. We just want to know which method from which class is calling which other method.

Our modus operandi is similar to keyword parsing. We start visiting all the call-expressions. Then we figure out what’s exactly being called.

This is done using TypeScript’s internal capability of maintaining Fully Qualified Names. So for each call, we get a FQN, which we then parse and figure out if it’s a keyword or not. If yes, then we must explore and expand the graph.

Once done, we parse the graph into a simpler structure. Something that says, this is the source, this is the destination.

https://github.com/metz-sh/simulacrum/blob/main/src/compiler/command-handlers/build-command/call-hierarchy-parser.ts