Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ branch_whitelist = ["main", "v*.x"]
ignore_merge_commits = true

# List of valid commit scopes
scopes = ["cli", "server", "ui", "document", "map", "theme", "skill", "docs", "deps", "config", "ci"]
scopes = ["cli", "server", "ui", "document", "map", "graph", "theme", "skill", "docs", "deps", "config", "ci"]

# Tag prefix for semantic versioning
tag_prefix = "v"
Expand Down
2 changes: 1 addition & 1 deletion src/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export function computeCoverage(input: CoverageInput): Coverage {
.map(([extension, n]) => ({ extension, files: n }))
.sort((a, b) => b.files - a.files || a.extension.localeCompare(b.extension)),
owners: owners.sort((a, b) => b.files - a.files || a.node.localeCompare(b.node)),
unresolved: files.reduce((n, f) => n + (input.graph.unresolvedByFile?.[f] ?? 0), 0),
unresolved: files.reduce((n, f) => n + (input.graph.unresolvedByFile[f] ?? 0), 0),
truncated: capped > 0,
};
record.verdict = coverageVerdict(record);
Expand Down
23 changes: 21 additions & 2 deletions src/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,17 @@ export interface Edge {
at: number;
}

/**
* Bumped whenever a field is added to CodeGraph. `graphAt` caches a built graph
* on disk keyed only by commit, so without this a graph written by an older
* binary loads with a field missing and every count derived from it silently
* reads as zero - a wrong number rather than an error, which is exactly what a
* document that states derived facts must never do.
*/
export const GRAPH_SCHEMA = 2;

export interface CodeGraph {
schema: number;
commit: string;
files: string[];
symbols: Sym[];
Expand Down Expand Up @@ -272,14 +282,23 @@ export async function buildGraph(cwd: string, commit: string): Promise<CodeGraph
edges.push({ from: from.id, to: target.id, kind: r.kind, at: r.line });
}
}
return { commit, files, symbols, edges, unresolved, unresolvedByFile, truncated };
return {
schema: GRAPH_SCHEMA,
commit,
files,
symbols,
edges,
unresolved,
unresolvedByFile,
truncated,
};
}

/** Build the graph, or reuse the one cached under `dir` for that commit. */
export async function graphAt(cwd: string, commit: string, dir: string): Promise<CodeGraph> {
const path = join(dir, "graph", `${commit}.json`);
const cached = await readJson<CodeGraph>(path);
if (cached && cached.commit === commit) return cached;
if (cached && cached.commit === commit && cached.schema === GRAPH_SCHEMA) return cached;
const g = await buildGraph(cwd, commit);
await writeJson(path, g);
return g;
Expand Down
3 changes: 2 additions & 1 deletion test/coverage.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { describe, it, expect } from "vitest";
import { computeCoverage, scopeTruncated } from "../src/coverage.ts";
import type { CodeGraph } from "../src/graph.ts";
import { GRAPH_SCHEMA, type CodeGraph } from "../src/graph.ts";

function graph(overrides: Partial<CodeGraph> = {}): CodeGraph {
return {
schema: GRAPH_SCHEMA,
commit: "deadbeef",
files: [],
symbols: [],
Expand Down
36 changes: 35 additions & 1 deletion test/graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { promisify } from "node:util";
import { mkdtemp, writeFile, mkdir } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildGraph, impact, architecture, capFiles } from "../src/graph.ts";
import { buildGraph, graphAt, impact, architecture, capFiles, GRAPH_SCHEMA } from "../src/graph.ts";
import { lineChanges } from "../src/git.ts";

const execFileP = promisify(execFile);
Expand All @@ -24,6 +24,40 @@ async function repo() {
return { dir, git };
}

describe("graphAt", () => {
it("rebuilds a graph cached by an older binary instead of trusting its shape", async () => {
const { dir, git } = await repo();
await writeFile(
join(dir, "src", "a.ts"),
`export function run() {\n return fetch("/x");\n}\n`,
);
await git("add", ".");
await git("commit", "-q", "-m", "base");
const commit = (await git("rev-parse", "HEAD")).stdout.trim();
// what a pre-upgrade thurview left on disk: right commit, missing fields
const cache = join(dir, ".cache", "graph", `${commit}.json`);
await mkdir(join(dir, ".cache", "graph"), { recursive: true });
await writeFile(
cache,
JSON.stringify({
commit,
files: [],
symbols: [],
edges: [],
unresolved: 0,
truncated: false,
}),
);
const g = await graphAt(dir, commit, join(dir, ".cache"));
// trusting the stale record would report zero unresolved references, which is
// a wrong number rather than an error
expect(g.schema).toBe(GRAPH_SCHEMA);
expect(g.unresolvedByFile).toEqual({ "src/a.ts": 1 });
const again = await graphAt(dir, commit, join(dir, ".cache"));
expect(again.unresolvedByFile).toEqual({ "src/a.ts": 1 });
});
});

describe("buildGraph", () => {
it("counts a call to an undefined name as unresolved instead of dropping it", async () => {
const { dir, git } = await repo();
Expand Down