Testing
npm install
npm test # vitest run, 30s per-test timeout
npm run build # tsc into build/
Fixtures (test/)
| File | What it exercises |
|---|---|
webpack-vue.js | A real-world-shaped webpack 4 JSONP chunk with a Vue component: deep nesting, minified names, numeric module ids |
modern.js | Modern syntax: classes, private fields, async generators, optional chaining, destructuring and so on |
component.tsx | TypeScript and JSX together |
Suites
src/index.test.ts: the API and the graph shape
- AST graph: a
File -> Program -> ExpressionStatement -> CallExpressionpath exists,props.callee.slug_refpoints at the realSONtarget, webpack module ids are found asNumericLiteralkeys, and line numbers are correct. - Persistence: import to disk, reopen,
add()a second file that reuses existing tables and pairs and adds new ones. save(): node and edge counts match after saving, the saved graph answers the same queries, and saving over an existing file is refused.code(): the right slice past a non-BMP character, a clear error for unknown ids, rejection of duplicate filenames, and it works aftersave()and reopening.- Hashing: with
cs-mast, every node has a 64-hex hash, different calls hash differently, the same identifier name hashes the same, different names hash differently, and the hash isn't duplicated insideprops. - Parser parity:
cs-mastandbabelgive the same per-type node counts on the webpack fixture, andbabelleaves every hashnull. - Type coverage: over
modern.jsandcomponent.tsx, more than 100 distinct node types, with every node of every type hashed. - Parse errors: a cs-mast parse error suggests
parser: "babel", andbabelrecovers from the same input.
src/declares.test.ts: DECLARES
A table of 36 snippet and expected-edge pairs, plus invariants over the fixtures. See DECLARES: Coverage.
src/scopes.test.ts: scopes
A table of 29 snippets, each mapping every bound name to its scope chain, plus provenance, idempotency, parser-parity and persistence tests. See Scopes: Coverage.
src/flow.test.ts: REFERS_TO, READS, WRITES, FLOWS_TO
There's one snippet per semantic case: the spec's numbered cases 1-21 plus 14 more. The edges() helper renders a case's edges as readable strings, labelling Identifiers as x, or x:decl for a DECLARES target, and other nodes as Type(code). So each expectation reads like the code:
expect(await edges("readWrite", "FLOWS_TO")).toEqual(["x:decl -> x", "x -> BinaryExpression(x + 1)", ...]);
flows() checks reachability with an ACYCLIC variable-length path. Test 21 checks over the fixtures for duplicates, self-edges and access provenance, and checks that re-import and babel parity give identical edges.
The A1-A20 tests are an acceptance suite for the semantic layer. Each relation should mean exactly one thing, with no property, control or call semantics mixed in. They add:
- static versus computed keys, with the property name declared so that a wrong resolution would show
obj[key] = xwriting no binding- an initializer versus a bare
let x; - a variable chain, and expression operators
- compound and logical results flowing back into the target
- destructuring and for-in/for-of with no flow, and with undeclared iterables having nothing to
READ - shadowing across global, param, block and catch scopes, checked by
line:col - sibling blocks
accessversusaccess_signature(everyxshares a hash)- reopening and re-adding a file never duplicating edges
- global invariants: the
SONtree intact, oneIN_SCOPEperDECLARES, and no tables from later milestones
The spec cases already cover the rest, and the file says which test covers which item. See References: Coverage and Value flow: Coverage.
src/calls.test.ts: CALLS, ARGUMENT_TO, RETURNS_TO
One snippet per call-resolution rule, both resolved and deliberately unresolved, then argument mapping, return ownership, interprocedural reachability (including recursion), the two end-to-end demos, determinism, babel parity, persistence and consistency invariants. It uses the shared edgeList() and reaches() helpers from src/test-utils.ts, which the build excludes. See Calls: Coverage.
Real bundles
Built bundles from the private js-recon-research corpus, covering webpack, React (webpack and Vite), Vue, Nuxt, SvelteKit, Angular and plain Node, are run through taintwire outside this repo. That run also makes targeted assertions on known code in them: a postMessage handler reaching eval across a call, the webpack require resolving while its module factories don't, and so on. The setup, per-bundle numbers, unresolved-call breakdown and performance measurements are in OVERNIGHT_REPORT.md at the repo root.
CI
.github/workflows/test.yml runs npm ci, npm run build and npm test on every push to main and every pull request. The matrix is ubuntu-latest and macos-latest, with Node 22 and 24, and fail-fast: false. @ladybugdb/core ships native binaries, so testing both operating systems matters.
Inspecting a graph by hand
graph.save("x.lbug") (or dbPath) writes a standard Ladybug 0.21 database. Any Ladybug tool of the same version can open it to browse the graph visually, for example Ladybug Explorer.