mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-11-10 09:46:45 +08:00
feat(ui): dead code and islands — what nothing reaches, and everything that could still reach it (CG-59)
A Dead code screen and a mark on the Map, both drawn from one derivation in src/graph/dead-code.ts so a second surface can never disagree with the first. The SQL half is four lines — no incoming edge but `contains`. It returns ~2 500 candidates on this repository and the shipped list is 20; everything in between is the feature. A candidate is dropped the moment there is any reason to believe something outside the graph reaches it: exported symbols and header declarations, test and generated files, abstract and interface members, anything carrying a `decorates` edge, overrides of an ancestor's member, names the language calls by itself, vendored directories, files nothing in the index reaches (those are islands, and the Map says so instead), names the resolver failed to resolve somewhere, and names shared with a symbol that IS referenced — the mis-resolution that leaves a used method with a self-edge and its twin with nothing. The last rule is the only one that is not a graph query: before a claim is made, the declaring file and every file that reaches it are read and the identifier counted, which is what catches the references the extractor never recorded (`this.handleMessage.bind(this)`, a call inside an object literal, a shorthand property). Every subtraction is counted and printed under the list with the scale it came from, and the caveat line above it never collapses: the claim is "no static reference in the index", not "unused". On the Map a module nothing depends on keeps its stroke and says so in its count line, and tool-generated files and modules recede to ink-4 there, in the map's file list, in search results and on the file screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2a0c6dc58f
commit
56dfdb0655
@@ -149,6 +149,41 @@ export function splitLines(content: string): string[] {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* The whole text of an INDEXED file, or `null` for anything unreadable.
|
||||
*
|
||||
* The dead code report's corroboration pass needs to count an identifier in a
|
||||
* file's text, and this module is the only one in `api/` that opens a file — so
|
||||
* the reader it uses lives here, behind the same chokepoint. Three refusals,
|
||||
* all answering `null` rather than throwing, because the caller's rule is
|
||||
* already "cannot read it → do not make the claim":
|
||||
*
|
||||
* - not in the index (the viewer never reads a file the graph does not know);
|
||||
* - outside the project (`resolveProjectFile` throws; caught here);
|
||||
* - bigger than `maxBytes`.
|
||||
*
|
||||
* Drift is deliberately NOT checked. The question being asked is "does anything
|
||||
* in this file write this name", and the file's current bytes are the better
|
||||
* answer to it than the bytes we indexed.
|
||||
*/
|
||||
export function readIndexedFileText(
|
||||
cg: CodeGraph,
|
||||
projectRoot: string,
|
||||
requested: string,
|
||||
maxBytes: number
|
||||
): string | null {
|
||||
try {
|
||||
const found = findIndexedFile(cg, requested);
|
||||
if (!found) return null;
|
||||
const absolute = resolveProjectFile(projectRoot, found.storedPath);
|
||||
const stats = fs.statSync(absolute);
|
||||
if (!stats.isFile() || stats.size > maxBytes) return null;
|
||||
return fs.readFileSync(absolute, 'utf8');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an indexed file has changed on disk since it was indexed — the same
|
||||
* verdict `/api/source` returns, for endpoints that must *flag* drift without
|
||||
|
||||
Reference in New Issue
Block a user