Skip to content

JS/TS: calls inside an anonymous callback are still dropped when there is no enclosing NAMED function (residual of #1740) #3124

Description

@cbartens

Summary

#1740 (closed COMPLETED, 2026-07-09) fixed calls inside anonymous arrow callbacks by attributing them to the enclosing function. That works — but only when an enclosing named function exists. When the nearest enclosing scope is itself anonymous, no calls edge is emitted at all.

#1740's own wording covers this case — "Nothing is emitted — not attributed to the enclosing function, not to the module node" — and the module-node half looks unimplemented.

This hits test files hardest, because it("…", async () => { … }) is exactly the shape with no named parent. Those are also the files you most want in a blast-radius answer.

Minimal repro

target.ts:

export function target(x: number) { return x; }

named-parent.ts — anonymous arrow, but inside a named function:

import { target } from "./target.js";
export function makeApp() {
  register("/a", async (c) => { return target(c); });
}
declare function register(p: string, h: (c: number) => unknown): void;

anon-parent.ts — byte-identical call, nearest enclosing scope is anonymous:

import { target } from "./target.js";
it("does the thing", async () => {
  register("/a", async (c) => { return target(c); });
});
declare function it(n: string, f: () => unknown): void;
declare function register(p: string, h: (c: number) => unknown): void;
graphify extract . --code-only
graphify affected "target()" --depth 2

Actual

- makeApp() [calls] named-parent.ts:L4
- anon-parent.ts [imports] anon-parent.ts:L1
- named-parent.ts [imports] named-parent.ts:L1

named-parent.ts gets a calls edge. anon-parent.ts gets only an imports edge — no calls edge exists for its identical call.

Expected

A calls edge for anon-parent.ts too, attributed to the module node when no named enclosing symbol is available.

What it cost on a real codebase

graphify affected "requireFamilyMemberHono()" --depth 1 returned 21 of the 25 source files containing a real call site. The four misses were all test files whose calls sit directly inside it(...):

packages/api/src/auth.test.ts
packages/api/src/auth.mcp.test.ts
packages/api/src/mcp/observability.test.ts
packages/api/src/mcp/token-revoke.integration.test.ts

The contrast is clean — auth.integration.test.ts:141 and auth.test.ts:134 are the same statement, const result = await requireFamilyMemberHono(c); inside app.get("/test", async (c) => {…}). The first is wrapped in function makeApp() and was found; the second sits in an anonymous it(...) and was not.

Why it matters

The result isn't empty, it's partial, and a partial answer is indistinguishable from a complete one. affected returns 21 confident, correct, well-formatted rows and silently omits four — so the failure mode is acting on it, not noticing it.

Environment

  • graphify 0.9.50
  • Python 3.14.5, macOS arm64
  • TypeScript 5.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions