Skip to content

Quickstart

codeanalyzer-typescript points at a TypeScript or JavaScript project and produces one typed artifact — its symbol table, call graph, and external symbols. Three steps below: build it, run it against a project, and read the result.

  1. Build the CLI.

    Terminal window
    git clone https://github.com/codellm-devkit/codeanalyzer-ts
    cd codeanalyzer-ts
    bun install
    bun run build # -> dist/codeanalyzer-typescript (standalone native binary)

    That compiles a standalone binary at dist/codeanalyzer-typescript — no Bun or Node needed to run it afterward. To skip the compile step, run from source instead with bun run start -- ….

  2. Run it against a project.

    Point --input at any TypeScript/JavaScript project root and --output at a directory for the result.

    Terminal window
    ./dist/codeanalyzer-typescript --input ./my-ts-project --output ./out

    By default the analyzer first materializes the project’s node_modules (so imported library calls resolve), walks every source file, builds the symbol table and call graph, and writes ./out/analysis.json. Intermediate state is cached under .codeanalyzer/ in the project.

  3. Read the result.

    analysis.json is a single TSApplication object with four top-level keys.

    Terminal window
    jq 'keys' ./out/analysis.json
    # [ "call_graph", "entrypoints", "external_symbols", "symbol_table" ]
    jq '.symbol_table | length' ./out/analysis.json # modules analyzed
    jq '.call_graph | length' ./out/analysis.json # call edges
    jq '.external_symbols | length' ./out/analysis.json # phantom library targets

    That’s it — a directory of source files is now a typed, queryable model of the program.

The call graph is a flat list of source -> target edges keyed by callable signature, so it drops straight into any graph library — here, Python’s networkx:

reachable.py
import json
import networkx as nx
app = json.load(open("./out/analysis.json"))
g = nx.DiGraph()
for edge in app["call_graph"]:
g.add_edge(edge["source"], edge["target"])
print(g.number_of_nodes(), "nodes,", g.number_of_edges(), "edges")
# Is a sink reachable from some caller? A graph query, not a guess.
# print(nx.has_path(g, caller_sig, sink_sig))

Edge endpoints are either a real TSCallable.signature from the symbol table or a TSExternalSymbol.signature for a call into a library — both are plain strings, so external boundaries show up as nodes in the graph rather than disappearing.

The default run is level 1: the TypeScript checker resolves the call graph, RTA expands virtual dispatch, and phantom nodes capture external calls — fast, no extra tooling. Level 2 adds CodeQL enrichment for the dynamic cases the checker can’t reach.

Terminal window
./dist/codeanalyzer-typescript --input ./my-ts-project --output ./out --analysis-level 2