fix(explore): surface synth constant-endpoint edges + precise redux-thunk dispatch resolution
Two fixes hardening the redux-thunk dynamic-dispatch synthesizer, found by validating it on real RTK repos beyond its trezor origin (uwave-web, session-desktop, octo-call): - Surfacing: buildFlowFromNamedSymbols filtered its named set to CALLABLE kinds, so synthesized edges between `constant` nodes (RTK thunks are `const X = createAsyncThunk(...)`) never entered the Flow / Dynamic-dispatch links scan — invisible at every tier, while the kind-agnostic Relationships section is off below 500 files. Add a `dynNamed` set (named constant/variable/ field nodes with a heuristic edge) feeding a shared collectSynthLinks into the "## Dynamic-dispatch links" section, threaded through the named.size<2 early-out (both-endpoints-constant hit return EMPTY first) and the main path. Main call-chain stays callable-only; the <500 budget tiers are untouched. No-op for callable flows. Plus a generic synthEdgeNote fallback so any synth hop reads "dynamic: <kind> @site", not a bare "[calls]". - Precision: reduxThunkEdges resolved a dispatched name by first-match-by-kind, so a thunk name colliding with a same-named service function linked to the wrong node (octo-call `leaveCall`). Prefer thunk-signature const > other const > same-file callable > first match. Tests: new explore-synth-constant-endpoints.test.ts (surfacing on a small repo) + a collision case in redux-thunk-synthesizer.test.ts. Full suite green (1605). Rationale + coverage backlog in docs/design/dispatch-synthesizer-backlog.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e5897d0334
commit
270e50655a
@@ -1681,9 +1681,20 @@ function reduxThunkEdges(queries: QueryBuilder, ctx: ResolutionContext): Edge[]
|
||||
while ((m = THUNK_DISPATCH_RE.exec(safe)) && added < THUNK_FANOUT_CAP) {
|
||||
const name = m[1]!;
|
||||
if (name === node.name) continue; // self-dispatch (recursive thunk) — skip
|
||||
const target = ctx
|
||||
// Resolve the dispatched name, PREFERRING the thunk/action-creator over a same-named
|
||||
// service function. `dispatch(X(...))` dispatches a thunk or an action-creator (both
|
||||
// `constant`s) — never an unrelated helper that merely shares the name. On octo-call,
|
||||
// `leaveCall` is BOTH a `createAsyncThunk` const AND a service function, and the bare
|
||||
// `.find()` picked the function (wrong). Order: thunk const > other const > same-file
|
||||
// callable > first match. A single candidate (no collision) is unaffected.
|
||||
const cands = ctx
|
||||
.getNodesByName(name)
|
||||
.find((n) => n.kind === 'constant' || n.kind === 'function' || n.kind === 'method');
|
||||
.filter((n) => n.kind === 'constant' || n.kind === 'function' || n.kind === 'method');
|
||||
const target =
|
||||
cands.find((n) => !!n.signature && THUNK_DECL_RE.test(n.signature)) ??
|
||||
cands.find((n) => n.kind === 'constant') ??
|
||||
cands.find((n) => n.filePath === node.filePath) ??
|
||||
cands[0];
|
||||
if (!target || target.id === node.id) continue;
|
||||
const key = `${node.id}>${target.id}`;
|
||||
if (seen.has(key)) continue;
|
||||
|
||||
Reference in New Issue
Block a user