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
+11 -2
View File
@@ -17,7 +17,12 @@
* candidate is dropped from the list the moment there is any reason to believe
* something outside the graph reaches it:
*
* - it is **exported** (something outside this repository may import it);
* - it is **exported** (something outside this repository may import it). That
* includes whatever a TypeScript `declare module 'x'` or `declare global`
* block declares, which TypeScript exports without the keyword: `interface
* Window` or chart.js's `PluginOptionsByType` merges into a type declared
* outside the index, and the runtime or the library reads it through that
* type, so nothing in the repository names it;
* - it lives in a **test** or a **generated** file (not code anyone deletes by
* hand);
* - it is **abstract** or declared on an interface (a declaration is dispatched
@@ -353,7 +358,11 @@ export interface DeadCodeExclusions {
tests: number;
/** In a tool-generated file. */
generated: number;
/** Exported, or declared in a header — reachable from outside this index. */
/**
* Exported, or declared in a header — reachable from outside this index. A
* TypeScript `declare module` / `declare global` declaration is exported
* implicitly: it merges into a library's or the global scope's type.
*/
exported: number;
/**
* In a language this index records no export marker for, so nothing here can