Summary
Methods defined on an exported object-literal constant are extracted as nodes, but no call site of the shape obj.method() ever links to them — not from another file via import, and not from the defining file itself. codegraph callers / impact return zero, and because this is a static property access (not one of the runtime-dispatch forms), boundary surfacing (#835) doesn't flag it either — the gap is fully silent.
Repro (v1.5.0, macOS arm64)
a.ts:
export const obj = { m() { return 1; } };
export class C { static s() { return 2; } }
export function sameFileCallers() { return obj.m() + C.s(); }
b.ts:
import { obj, C } from "./a";
export function crossFileCaller() { return obj.m() + C.s(); }
After codegraph init:
$ codegraph callers m
ℹ No callers found for "m" # expected 2 (sameFileCallers, crossFileCaller)
$ codegraph callers s
Callers of "s" (2):
function sameFileCallers
a.ts:3
function crossFileCaller
b.ts:2
Identical call shapes in the same expressions; the only difference is the container kind.
Real-world scale
In a real TypeScript repo, one exported object-literal serves as the app's API-wrapper namespace (100+ methods, called from everywhere). Measured there: one method with 170+ call sites reports 0 callers, and the wrapper's core call method has ~100 call sites in the defining file alone with ≈0 resolved. export const api = {...} is a common way to organize an API surface in TS, so callers/impact systematically under-report on repos using it.
Root cause
src/resolution/import-resolver.ts:
const STATIC_MEMBER_CONTAINERS = new Set<Node['kind']>([
'class', 'struct', 'union', 'interface', 'enum', 'trait', 'protocol',
]);
Container.member resolution (#825) only descends into those kinds. A constant container resolves to the constant node and stops — the member node is never reached, so the edge is never minted (the same-file path included).
Suggested direction
Extending the #825 mechanism to constant containers whose initializer is an object literal looks natural: members are already looked up by ${container.qualifiedName}::${member} scoped to the container's file, so name+file scoping should keep the same decoy-safety properties validated for chained resolution in #750. Happy to work on a PR if this direction sounds right.
Summary
Methods defined on an exported object-literal constant are extracted as nodes, but no call site of the shape
obj.method()ever links to them — not from another file via import, and not from the defining file itself.codegraph callers/impactreturn zero, and because this is a static property access (not one of the runtime-dispatch forms), boundary surfacing (#835) doesn't flag it either — the gap is fully silent.Repro (v1.5.0, macOS arm64)
a.ts:b.ts:After
codegraph init:Identical call shapes in the same expressions; the only difference is the container kind.
Real-world scale
In a real TypeScript repo, one exported object-literal serves as the app's API-wrapper namespace (100+ methods, called from everywhere). Measured there: one method with 170+ call sites reports 0 callers, and the wrapper's core
callmethod has ~100 call sites in the defining file alone with ≈0 resolved.export const api = {...}is a common way to organize an API surface in TS, so callers/impact systematically under-report on repos using it.Root cause
src/resolution/import-resolver.ts:Container.memberresolution (#825) only descends into those kinds. Aconstantcontainer resolves to the constant node and stops — the member node is never reached, so the edge is never minted (the same-file path included).Suggested direction
Extending the #825 mechanism to constant containers whose initializer is an object literal looks natural: members are already looked up by
${container.qualifiedName}::${member}scoped to the container's file, so name+file scoping should keep the same decoy-safety properties validated for chained resolution in #750. Happy to work on a PR if this direction sounds right.