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>
This commit is contained in:
Colby Mchenry
2026-10-07 08:21:08 +00:00
committed by GitHub
parent d8a7f868b3
commit e0bbb6620f
9 changed files with 412 additions and 9 deletions
+1 -1
View File
@@ -355,7 +355,7 @@ entry-points panel.
**The caveat is part of the screen, not a note on it.** A persistent 11.5px `--ink-3` line sits above the rows, between two
hairline rules, and never collapses or dismisses: *"No static reference in the index — dynamic use is possible."* Under the list,
every reason a candidate was left off is printed with its count ("1 677 in test files", "378 exported, or declared in a header",
every reason a candidate was left off is printed with its count ("1 677 in test files", "378 exported, declared in a header, or merged into a library or global type",
"40 overriding a member declared further up"), preceded by the scale — *"2 494 symbols in this index carry no incoming reference
at all; 2 474 of them were left off this list."* Twenty rows drawn from twenty candidates and twenty drawn from two and a half
thousand are different screens and only that sentence tells them apart.
+2
View File
@@ -179,6 +179,8 @@ These describe `codegraph ui` and its screens. They were taken out of `## [Unrel
- **Dead code no longer lists methods a framework calls for you.** A method with a framework's decorator on it — Angular's `@HostListener` or `@HostBinding`, NestJS's `@Cron`, `@OnEvent` or `@Process`, TypeORM's `@BeforeInsert`, Spring's `@InitBinder` — was listed as unused whenever the decorator came from a package outside your project. So was a lifecycle hook like Angular's `ngOnInit` or NestJS's `onModuleInit` on a class that skips the `implements` clause, though the framework calls it by name all the same. Both are now left off and counted under the list. What counts as a hook is learned from your own classes that do write the interface out, not from a built-in list, so an unused helper on the same component is still listed. Nothing needs a re-index.
- **Dead code no longer lists the types you add to a library's or the browser's.** An interface written inside `declare module 'chart.js' { … }` or `declare global { … }`, like chart.js's `PluginOptionsByType`, TanStack Router's `Register`, the browser's `Window`, or a custom element's entry in `HTMLElementTagNameMap`, merges into a type declared outside your project. The library or the browser reads it from there, so nothing in your code names it, and every one was listed as unused. TypeScript exports what these blocks declare without an `export` keyword, and CodeGraph now records them the same way, so they are left off and counted with the exported symbols. A `declare namespace` in an ordinary module file still belongs to that file and is checked as before. Re-index TypeScript projects after upgrading.
- **One slow file can no longer freeze the viewer.** Code is highlighted with the same grammars the index uses. The COBOL grammar never finishes a line like ` .`, which free-format COBOL paragraphs end with, so opening a Flow through cobolcraft froze `codegraph ui`, and every page after it waited forever. Highlighting now runs where it can be stopped: a piece of code that takes too long is shown unhighlighted, and everything else keeps working.
- **The Map opens on a picture, not a single box.** A project whose code sits in one flat folder, like Express's `lib/`, an R package's `R/`, an Erlang app's `src/` or fmt's `include/fmt/`, opened as one box with nothing to say. It now opens on the whole repository when that draws more. Files at the repository's top level also count when the Map picks where the program lives: git's hundreds of top-level `.c` files had made `builtin/` look like most of the code.