Commit Graph

4 Commits

Author SHA1 Message Date
Colby Mchenry e0bbb6620f fix(dead-code): a type that declare module or declare global merges outward is not dead (#2418)
The viewer's Dead code screen listed TypeScript interfaces that augment a
type declared outside the project: chart.js's `PluginOptionsByType` in
`declare module 'chart.js' { … }` (ghostfolio), `interface Window` in
`declare global { … }` (angular-realworld). The library or the runtime
reads them through the type they merge into, so nothing in the repository
names them, and the index recorded them as unexported file-level
interfaces.

TypeScript exports what such a body declares without the keyword. The
binder makes an ambient module body an export context unless it holds an
export declaration (`export {}`, `export =`, `export default x`), and a
namespace nested in one is ambient too. Both extractors now set isExported
that way: isAmbientExport in languages/typescript.ts, shared by TSX and
ArkTS, and is_ambient_export in the kernel's tsjs walker. The exported rule
then drops them, counted under `exported`, and that count's label now
names them. `declare namespace X` is unchanged: in a module file X belongs
to that file.

The resolver sees them as exported too. A file holding `declare global`
was already treated as exporting, so nothing moves there. A `declare
module` member becomes reachable across files by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:21:08 +00:00
Colby Mchenry 9a515c3e64 fix(dead-code): a method a framework calls by decorator or by name is not dead (#2405)
The viewer's Dead code screen listed two kinds of framework-called methods.

A method whose decorator resolves outside the project (`@HostListener`,
`@Cron`, `@OnEvent`, `@Process`, `@BeforeInsert`, `@InitBinder`) has no
`decorates` edge and no `node.decorators`, only the resolver's unresolved
`decorates` row. That row now counts as a decorator, the same evidence the
opaque-ancestor rule reads for an `extends`/`implements` row, under the
existing `decorated` count.

A lifecycle hook on a class that skips the `implements` clause (`ngOnInit`
on a `@Component` without `implements OnInit`) had no evidence at all. A new
`hooks` rule drops a TypeScript method when its class carries a decorator and
an interface outside the index carries the name. What the interface carries is
read off the classes that write it out: every one has the method, declared or
inherited, at least two and more than half declare it, and more than half of
them carry a decorator. TypeScript only, because `implements` is erased there
and a framework can only be finding the hook by name.

The ancestor walk moves out of overrideCandidates into ancestorsOf, shared by
both rules. Declarations are read from the candidate list rather than searched
by name: a hook is never called from inside the index, and a name some
declaration of which is called is the ambiguous-name rule's anyway.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:27:27 +00:00
Colby Mchenry 52b854d44a fix(dead-code): propagate external ancestor uncertainty to descendants (#1973) (#2007)
* fix(dead-code): treat an ancestor outside the index as opaque (#1973)

An override is excluded from the dead-code report when its ancestor is in
the graph, or is present but declares no members we can read. An ancestor
outside the project (React.Component, stream.Transform, a NestJS
interface) creates no edge at all, so a lifecycle method the framework
calls looked like it overrode nothing and was listed as unreferenced.
The resolver does record such an ancestor, as a failed extends /
implements row. A container with one, or a project ancestor of one that
has it, now counts as having an opaque ancestor, and its members are
excluded under "overriding".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(dead-code): propagate external ancestor uncertainty to descendants (#1973)

External ancestors leave unresolved inheritance references rather than graph edges.
Treat those ancestors as opaque and propagate uncertainty through every descendant root.
Track visited ancestor/root pairs while preserving cycle protection and the depth bound.
Cover direct and three-level inheritance, unused controls, and batched query behavior.

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: danusha2345 <ewidusoc498@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 13:04:17 +00:00
Colby McHenry 56dfdb0655 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>
2026-08-27 07:57:00 -05:00