fix(typescript): name HOC-wrapped const declarations (forwardRef / memo / useCallback / useMemo / observer)#1261
Conversation
…mo / useCallback / useMemo / observer / debounce) Follow-up to issue abhigyanpatwari#1166 / PR abhigyanpatwari#1175. After fixing HOF callbacks (Promise fan-out, queryFn pair-arrows, multi-action Zustand stores) and JSX-as-call, the dominant residual 0%-capture pattern in real React UI codebases was the HOC-wrapped variable declaration: const Button = React.forwardRef((props, ref) => { ... }) const Card = memo((props) => { ... }) const handleClick = useCallback(() => { ... }, []) const computed = useMemo(() => { ... }, []) const debouncedSearch = debounce((q) => { ... }, 250) All share the AST shape `lexical_declaration > variable_declarator > call_expression > arguments > arrow_function`. Pre-fix, neither the registry-primary `query.ts` nor the legacy `tree-sitter-queries.ts` had a `@declaration.function` pattern matching this shape, and the legacy DAG's `tsExtractFunctionName` only walked `variable_declarator` and `pair` parents — `arguments` parents fell through with `funcName = null`. Result: every shadcn/Radix component, every memoised React component, and every `useCallback` / `useMemo` callback bound to a const registered as anonymous; calls inside attributed to the file. Sourcerer-fe audit: ~296 declarations affected (~57 forwardRef + ~21 memo + ~161 useCallback + ~57 useMemo). Fix: - 4 new tree-sitter patterns in `languages/typescript/query.ts` (registry-primary), anchored on the inner arrow_function / function_expression — same anchor discipline as the existing `lexical_declaration` and `pair` patterns from PR abhigyanpatwari#1175. - 8 mirrored patterns in `tree-sitter-queries.ts` (4 in TYPESCRIPT_QUERIES, 4 in JAVASCRIPT_QUERIES) for the legacy DAG and the CI parity gate. - New `arguments`-parent branch in `tsExtractFunctionName` that walks `arguments → call_expression → variable_declarator` and returns the const's name. Three guards keep it strictly scoped to HOC-wrapped declarations; bare statement-level HOC calls fall through anonymous. Tests: - 11 integration tests + 9 minimal TS/TSX fixtures exercising forwardRef / memo / useCallback / useMemo / observer / debounce, with positive (named-Function + correct CALLS edge), negative (no phantom Functions for unbound HOCs, no phantom self-loops, no first-sibling-wins leakage), and cross-pollination assertions. - 8 new unit tests in `call-attribution-issue-1166.test.ts` pinning the legacy-DAG path: 6 attribution tests + 2 @definition.function capture tests. Trade-off documented inline: chained array-method declarations (`const x = arr.find((y) => p(y))`) match the same shape and produce a mostly-harmless phantom `Function:x` with one outgoing edge. The false-positive cost is negligible vs. the React UI coverage gain. Verification: - 11/11 typescript-hoc-wrapped (registry-primary) - 26/26 call-attribution-issue-1166 (8 new + 18 pre-existing) - 266/266 across all 4 typescript resolver test files (registry) - 236/236 typescript.test.ts on legacy DAG (CI parity gate) - 1693/1693 across all non-Kotlin/Swift resolver test files - tsc --noEmit clean; prettier clean; eslint clean (no new warnings) Co-authored-by: Cursor <cursoragent@cursor.com>
|
@ReidenXerx is attempting to deploy a commit to the NexusCore Team on Vercel. A member of the Team first needs to authorize it. |
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 7842 tests passed 1 test(s) skipped — expand for details
Code CoverageTests
📋 View full run · Generated by CI |
|
Live re-index of Sourcerer-fe (525 source files, post-PR-#1175 + this PR's HOC fix vs. pre-PR-#1175 baseline) — combined uplift on the React UI codebase that originally surfaced this regression in the bug report:
Concrete spot-check — every shadcn/Radix UI primitive in PR description updated with the full breakdown including a per-table comparison and the micro-validation against the |
|
Claude finished @magyargergo's task in 6m 34s —— View job PR #1261 Code Review — HOC-wrapped const declarationsReview bar for this PR
Issue and PR contextIssue #1166 identified that ~75% of CALLS edges were missing in TypeScript HOF/callback-heavy codebases. The root causes were: PR #1175 fixed pair-arrow naming, Promise fan-out, TanStack This PR (#1261) closes the remaining dominant blind spot from the same audit: Changed files (14):
CI status: All checks passed on head SHA Known tradeoffs documented:
Understanding of the changeWhy arrows inside Fix structure:
Findings[medium] Array-method false-positive is documented but has no assertion test
[medium] Nested HOC fixture (
|
| DoD item | Status |
|---|---|
HOC-wrapped consts produce named Function nodes |
✅ satisfied — 7 fixtures tested in integration, unit tests verify @definition.function capture |
| Calls inside wrapped arrow attribute to named function, not File | ✅ satisfied — integration tests assert CALLS source by name; self-loop negative tests present |
| Registry-primary and legacy TS paths aligned | ✅ satisfied — 4+4+4+4 patterns mirrored; export_statement forms covered in legacy; variable_declaration gap is minor (see finding 4) |
| JS legacy query behavior updated without regressing existing JS capture | ✅ satisfied — JAVASCRIPT_QUERIES updated; broad all-language test suite passed; tsExtractFunctionName shared |
| Bare statement-level HOC calls remain anonymous / File-attributed | ✅ satisfied — negative-bare-call.ts fixture + integration + unit tests cover this |
| Nested HOCs do not create phantom wrong edges | Function:Wrapped edges |
| Known false-positive patterns tested/documented | |
| Existing HOF/JSX-as-call/Zustand/TanStack patterns unchanged | ✅ satisfied — regression tests pass; new arguments branch guards correctly against Promise executor, Zustand nested chains, TanStack patterns |
| CI/typecheck/parity validation green | ✅ satisfied |
TypeScript / JavaScript attribution assessment
- HOC-wrapped const naming: Correct. The
arguments → call_expression → variable_declaratorguard chain is tight.new_expressioncorrectly excluded. Bare statement-level calls correctly excluded. Destructuring declarators ({ a, b } = ...) correctly fall through to null (noidentifiername node). - CALLS source attribution: Correct. Anchor-on-inner-arrow discipline ensures the def lands in the arrow's own scope and hoists the binding to the parent. Both
findEnclosingFunctionId(parse-worker) andfindEnclosingFunction(call-processor) usetsExtractFunctionName, so both paths agree. - Registry-primary query patterns: Well-formed. Four patterns covering
lexical_declaration × variable_declaration × arrow_function × function_expression. TSX is handled by the same patterns (the grammar switch is at the parser level, not the query level).export constis handled because tree-sitter pattern matching is positional/subtree, not anchored —(lexical_declaration ...)matches innerlexical_declarationregardless ofexport_statementwrapper. - Legacy TS/JS query patterns: Well-formed. Four TS patterns + four JS patterns including explicit
export_statementwrappers. Capture names (@name,@definition.function) match legacy parser expectations. Minor: novariable_declarationHOC mirror (see finding 4). tsExtractFunctionName: Third branch is correctly guarded. Shared between TS and JS providers (both useextractFunctionName: tsExtractFunctionName).- Exported declarations: ✅ Unit test at line 516 explicitly asserts
export const Exported = forwardRef(...)is captured. - Function expression support: ✅ Both
arrow_functionandfunction_expressionforms covered in all four pattern layers (registry-primary, legacy TS, legacy JS, resolver branch). - Bare-call negative behavior: ✅ Solid.
expression_statementparent breaks thevariable_declaratorguard; integration test + unit test both verify. Thewrap(memo(...))case (doubly-nested bare calls) correctly returns null because the innercall_expression.parentisargumentsnotvariable_declarator. - Nested HOC behavior: Correctly excluded — the deepest arrow's
call_expression.parentisarguments(notvariable_declarator) for doubly-wrapped forms. But no integration test asserts this (see finding 2). - Array-method false-positive tradeoff: Correctly analyzed. The
Function:xfromconst x = arr.find(...)is harmless in terms of incoming CALLS (x is a value, never invoked). OutgoingFunction:x → predicateis a minor mis-attribution. Tradeoff is proportionate. Missing assertion test (see finding 1). - Multi-arrow argument behavior: Dedup claim is plausible based on understanding the registry's qualified-name dedup logic. No test verifies it.
Test assessment
- Integration fixture coverage: Strong. 7 HOC wrappers × named-Function + named-CALLS assertions. Sibling const cross-attribution test (no first-sibling-wins). Self-loop negative test. Bare-call negative test. Covers
React.forwardRef(member-expression callee), bareforwardRef,memo,useCallback,useMemo,observer,debounce. - Unit attribution coverage: Strong. 6 new attribution tests (bare HOC, member-expression HOC, useCallback, memo, first-sibling isolation, bare statement-level negative). 2 definition-capture tests. Tests use the same
collectCallAttributionsharness that mirrors productionfindEnclosingFunctionIdlogic. - JS coverage: Indirect. JAVASCRIPT_QUERIES updated with 4 new patterns;
tsExtractFunctionNameshared. No dedicated JS fixture, but broad all-language test run passed. Acceptable. - Legacy parity coverage: ✅ PR body claims
REGISTRY_PRIMARY_TYPESCRIPT=0parity run passed (236/236). CI also ran full suite. - Negative tests: Solid. Bare-call negative in both unit and integration. Self-loop check. No-first-sibling assertion.
- Would tests fail before this PR? Yes. All named-CALLS assertions (e.g.,
Button → cn,handleClick → doStuff) would fail because the arrows were anonymous and CALLS attributed to File. The Function-node existence test would also fail. - Gaps: No test for array-method false-positive, no test for multi-arrow dedup, no integration test for
nested.tsxbehavior.
Validation assessment
- CI: Green on head
8d2eae8. 7772/7773 tests passed; 1 pre-existing skip documented. Code coverage improved +0.2% statements, +0.3% functions. - Docker: Green per PR comments.
- Typecheck:
tsc --noEmitclean per PR body and CI typecheck stage. - Focused tests: 11/11 HOC integration, 26/26 unit (8 new + 18 existing).
- Broad resolver tests: 1693/1693 across all non-Kotlin/Swift languages.
- Legacy DAG parity: Claimed 236/236 with
REGISTRY_PRIMARY_TYPESCRIPT=0; not directly visible in CI output but consistent with green run. - Live re-index: Empirically validated on 525-file Sourcerer-fe codebase. Metrics are compelling (+936 Function nodes, −45 File-rooted CALLS) but these are auxiliary evidence, not a substitute for targeted regression tests.
- Not verified directly: Multi-arrow dedup claim (reasoned, not tested). First-arrow CALLS loss on multi-arrow dedup (not verified by test). Nested HOC "no phantom edges" claim (asserted only by PR description, not by test code).
Compatibility and rollout assessment
- Graph schema compatibility: No schema changes. New
Functionnodes andCALLSedges are additive to the existing graph model. - Re-index requirement: Indexed repos must re-run
npx gitnexus analyzeto discover the new nodes. Existing graphs remain valid (no migration needed). Clearly stated in PR. - Expected impact on React/TS codebases: High positive impact. HOC-wrapped declarations are the dominant authoring style for React UI primitives. Every
forwardRef,memo,useCallback,useMemoconst previously invisible togitnexus_context/gitnexus_impactis now a first-classFunctionnode with outgoing CALLS. - Release-note recommendation: Accurate in PR body. The note correctly scopes to HOC-wrapped consts, names the known false-positive, and quantifies the impact.
- Known limitations / follow-ups:
- Deeply-nested HOCs (
memo(forwardRef(...))) — out of scope, correctly documented, needs integration test assertion. - Array-method callbacks (
arr.find((y) => ...)) — accepted false positive, needs an assertion test to pin the behavior. - Multi-arrow callback dedup (
call(a1, a2)) — documented, needs an assertion test.
- Deeply-nested HOCs (
Final verdict
production-ready with minor follow-ups
The core attribution fix is architecturally sound and correctly implemented across all three touch points (resolver, registry-primary query, legacy queries). The arguments → call_expression → variable_declarator guard chain is tight enough to exclude new Promise, bare statement-level calls, and doubly-nested HOC chains. Anchor discipline is correct — inner-arrow placement ensures CALLS attribute to the named const, not to File. CI is green on all 7772 tests. The three medium findings are all about missing assertion tests for documented trade-offs, not about incorrect behavior. The array-method false-positive is harmless in practice (no incoming CALLS), the nested HOC limitation is correctly excluded, and multi-arrow dedup only affects a rare API shape. None of these block correctness of the primary fix. The recommended follow-up tests would strengthen the documentation-to-test contract but are not prerequisite for merge.
|
@ReidenXerx could you please look at these findings? 🙏 please ensure that the CI is healthy as well |
…rity gap Addresses the four findings on PR abhigyanpatwari#1261 (Claude bot review for abhigyanpatwari#1261). All findings flagged missing assertion tests for behaviour already documented in code comments — none reported a real bug. The verdict was "production-ready with minor follow-ups"; these tests strengthen the documentation-to-test contract. [medium abhigyanpatwari#1] Array-method false-positive Pin `const found = items.find((item) => predicate(item))` → `predicate.attributedTo === 'found'` as an accepted FP. The const is a value, never invoked, so no incoming CALLS edge ever points at it; the outgoing edge is a minor mis-attribution we accept rather than maintain a HOC allowlist. [medium abhigyanpatwari#2] Nested HOCs (`memo(forwardRef(...))`) — no phantom Function:Wrapped Two integration tests in `typescript-hoc-wrapped.test.ts`: 1. `Wrapped` is NOT a Function node (the outer call's first arg is a call_expression, not an arrow — no @declaration.function pattern matches the outer shape). 2. The deepest arrow's `helper()` call is NOT attributed to Function:Wrapped (the deepest arrow is anonymous because call_expression.parent is `arguments`, not `variable_declarator`), and no Function-sourced CALLS originate from `nested.tsx`. [medium abhigyanpatwari#3] Multi-arrow argument dedup Pin `const x = call(() => first(), () => second())` — both arrows share the same `arguments → call_expression → variable_declarator` ancestor chain on the legacy DAG, so both attribute to "x". Documents the registry-primary dedup story alongside. [low abhigyanpatwari#4] `var X = HOC(...)` parity gap Registry-primary `query.ts` had `(variable_declaration ...)` HOC patterns but legacy `tree-sitter-queries.ts` (TS + JS) did not. Closes the gap by mirroring two `(variable_declaration ...)` HOC patterns into both legacy sections so the parity gate stays tight even if a codebase mixes `var X = HOC(...)` with `const X = HOC(...)`. Validation - Targeted: 41/41 (28 unit + 13 integration) on registry-primary. - Broader TS suite: 60/60 across 4 resolver test files. - CI parity gate (`typescript.test.ts`): 236/236 on legacy DAG and 236/236 on registry-primary. - Prettier clean. ESLint clean (5 pre-existing non-null-assertion warnings in the test file, unrelated). tsc --noEmit clean. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@magyargergo thanks for the review — all four follow-ups addressed in What changed
Validation
CI should re-run automatically on the new commit. Will keep an eye on it. |
|
Claude finished @magyargergo's task in 4m 17s —— View job PR #1261 Code Review — HOC-wrapped const declarationsReview bar for this PR
Issue and PR context consideredIssue #1166 identified ~75% CALLS-edge miss rate in TypeScript codebases with HOF/callback idioms. Two root causes: PR #1175 fixed pair-arrow naming, Promise fan-out, TanStack This PR (#1261) closes the HOC-wrapped blind spot across both pipeline paths (registry-primary + legacy) and the resolver layer. 14 files changed: CI status: Both CI and Docker Build & Push workflows are green on head Known trade-offs: (1) Array-method callbacks ( Understanding of the changeBefore this PR, The fix adds a third resolver branch that walks Findings[low] Registry-primary
|
| DoD item | Status |
|---|---|
HOC-wrapped consts produce named Function nodes |
✅ satisfied — 7 HOC-wrapped consts in fixtures, each asserted as Function by getNodesByLabel(result, 'Function'); unit test pins @definition.function captures for all 5 wrapper forms + function-expression |
| Calls inside wrapped arrow attribute to named function, not File | ✅ satisfied — all positive CALLS tests filter by source === constName; nested HOC integration tests assert no Function-sourced CALLS from nested.tsx |
| Registry-primary and legacy TS paths aligned | ✅ satisfied — lexical_declaration × variable_declaration × arrow_function × function_expression covered in both; export_statement forms in legacy, implicit subtree match in registry-primary (minor note above); CI parity gate claimed green |
| JavaScript legacy queries updated without regressing | ✅ satisfied — JAVASCRIPT_QUERIES carries 6 matching HOC patterns; tsExtractFunctionName shared; broad all-language run 1693/1693 |
| Bare statement-level HOC calls remain anonymous / File-attributed | ✅ satisfied — unit test asserts doSomething.attributedTo === null; integration test asserts fromFile.length > 0; wrap(memo(...)) doubly-bare case excluded by variable_declarator guard |
| Nested HOCs do not create phantom wrong edges | ✅ satisfied — two integration tests: Wrapped NOT in Function nodes; all nested.tsx CALLS source from File, none from Function:Wrapped |
| Known false-positive patterns tested/documented | ✅ satisfied — array-method FP pinned in unit test (predicate.attributedTo === 'found', with explanation comment); multi-arrow pinned in unit test (first and second both attribute to x); extensive comments in query.ts and typescript.ts |
| Existing HOF/JSX-as-call/Zustand/TanStack patterns unchanged | ✅ satisfied — new arguments branch is a third branch added after variable_declarator and pair; existing branches untouched; 266/266 TS resolver families passed |
| CI, Docker, typecheck, focused tests, resolver parity green | ✅ satisfied — CI + Docker both green on head 861cbe6; tsc clean; 7842/7843 tests (1 pre-existing skip) |
TypeScript / JavaScript attribution assessment
HOC-wrapped const naming: Correct. The arguments → call_expression → variable_declarator guard chain is tight. new_expression is excluded (the check is callExpr.type !== 'call_expression'). Bare statement-level calls are excluded (declarator.type !== 'variable_declarator' fires when parent is expression_statement). Nested HOCs are excluded (inner call_expression.parent is arguments again, not variable_declarator). Destructuring declarators are excluded (no identifier name node).
CALLS source attribution: Correct. Inner-anchor discipline in registry-primary ensures pass2AttachDeclarations.atPosition resolves to the arrow's own scope and rangesEqual triggers the binding hoist to the parent scope. tsExtractFunctionName ensures both findEnclosingFunctionId (parse-worker) and findEnclosingFunction (call-processor) agree on attribution. Integration tests assert source === constName on CALLS edges, not just Function node existence.
Registry-primary query patterns: Four patterns, anchor on inner arrow_function/function_expression. Consistent with the existing lexical_declaration and pair patterns established in PR #1175. export const handled via implicit subtree matching (verified by forward-ref.tsx fixture using export const Button). No duplication with existing direct const fn = () => {} patterns — the intermediate call_expression > arguments distinguishes them.
Legacy TS/JS query patterns: Six patterns each (lexical + export_statement + variable_declaration × 2). Anchor on outer lexical_declaration/export_statement — consistent with all pre-existing legacy patterns. @name and @definition.function capture names match legacy parser expectations. var form mirrored. No conflicts with @definition.const/@definition.variable — parse-worker dedup handles the overlap.
tsExtractFunctionName: Third branch is tightly guarded and returns cleanly at every guard. Shared between TypeScript and JavaScript providers.
Exported declarations: Integration fixture forward-ref.tsx uses export const Button = React.forwardRef(...). Unit test (line 516) covers legacy path explicitly.
Function expression support: Both arrow_function and function_expression covered at all four levels (resolver, registry-primary, legacy TS, legacy JS). Unit test const Legacy = wrap(function (x) { ... }) asserts Legacy in captured names.
Bare-call negative behavior: Solid. Both unit test (attributedTo: null) and integration test (CALLS from File) cover this. The doubly-nested bare call wrap(memo(({ y }) => { ... })) is correctly excluded because memo(...) 's call_expression.parent is arguments (of wrap), not variable_declarator.
Nested HOC behavior: Correctly excluded — documented in nested.tsx comment block, asserted by two integration tests. No phantom edges.
Array-method false-positive: const found = items.find((item) => predicate(item)) → Function:found → predicate. Tested and accepted. The Function:found node has zero incoming CALLS (never invoked), so it is graph-isolated on the incoming side. The outgoing Function:found → predicate is a minor mis-attribution with negligible practical impact.
Multi-arrow argument behavior: const x = call(() => first(), () => second()) → both arrows attribute to x via legacy path (tested). Registry-primary dedup story is reasoned in a comment (second-write-wins on qualified-name collision). Rare in real APIs.
Test assessment
Integration fixture coverage: 9 fixtures, 13 tests. Every positive test filters by both sourceFilePath and source, verifying attribution is correct and not merely that edges exist. Covers: React.forwardRef (member-expression callee), bare forwardRef, memo, useCallback (two sibling consts), useMemo, observer, debounce. Self-loop negative, bare-call negative (with File-attribution assertion), cross-pollination, nested HOC limitation (two tests). Function-node existence tested as a separate assertion from CALLS attribution.
Unit attribution coverage: collectCallAttributions harness mirrors the production findEnclosingFunctionId walk exactly. 6 attribution tests (bare HOC, member-expression HOC, useCallback, memo, sibling isolation, bare-statement negative) + 2 definition-capture tests + 2 trade-off pins (array-method FP, multi-arrow dedup). Test harness is robust — no brittle line/column values.
JS coverage: No dedicated JS fixture, but tsExtractFunctionName is shared and JAVASCRIPT_QUERIES carries matching patterns. Broad all-language test run (1693/1693) includes the existing JS resolver tests which pass. Acceptable — adding a minimal JS fixture would add confidence but is not a blocking gap.
Legacy parity coverage: PR claims 236/236 with REGISTRY_PRIMARY_TYPESCRIPT=0. The CI parity gate is a separate run and passes per PR description (not directly visible in the CI action output shown in comments, but consistent with the overall green status).
Negative tests: Solid. Bare-call negative is tested at both unit and integration levels. Self-loop check is explicit (filters source === constName && target === wrapperName). No-first-sibling assertion checks that no Function-sourced strays from siblings appear.
Would tests fail before this PR? Yes, conclusively. All positive CALLS assertions (Button → cn, handleClick → doStuff, etc.) would fail because the arrows were anonymous and CALLS attributed to File. The Function-node existence test for each HOC-wrapped const would also fail.
Validation assessment
- CI (head
861cbe6): ✅ Green — confirmed via CI API. Both CI and Docker Build & Push passed on 2026-05-03. - Docker: ✅ Green.
- Typecheck: ✅
tsc --noEmitclean per PR description and CI typecheck stage. - Focused tests: 13/13 HOC integration, 28/28 unit (covering the HOC-specific describe block); 266/266 TS resolver families.
- Broad resolver tests: 1693/1693 across all non-Kotlin/Swift languages (Kotlin/Swift skipped per
AGENTS.md§ Gotchas — acceptable). - Legacy DAG parity: 236/236 with
REGISTRY_PRIMARY_TYPESCRIPT=0per PR description. - ESLint: Clean; 5 pre-existing
Forbidden non-null assertionwarnings in unchanged unit-test code. - Live re-index: Compelling empirical evidence (+936 Function nodes, −45 File-rooted CALLS, +1,018 CALLS total on Sourcerer-fe). Treated as auxiliary evidence consistent with the targeted tests.
- Not verified directly: Registry-primary dedup second-wins claim for multi-arrow (reasoned, not tested at registry level). Acceptable given the pattern is rare and the legacy path is tested.
Compatibility and rollout assessment
Graph schema compatibility: No schema changes. New Function nodes and CALLS edges are additive.
Re-index requirement: Indexed repos must re-run npx gitnexus analyze. Existing graphs remain valid (no migration). Clearly documented in PR.
Expected impact on React/TS codebases: High positive impact. Every React.forwardRef, memo, useCallback, useMemo, observer, debounce-wrapped const — the dominant authoring style for React UI primitives — is now a first-class Function node with resolved outgoing CALLS. Shadow impact on gitnexus_context/gitnexus_impact for these nodes goes from zero to complete.
Release-note recommendation: Accurate in PR body. Correctly scopes to HOC-wrapped consts, names the array-method false-positive, quantifies the impact range.
Known limitations / follow-ups:
- Doubly-nested HOCs (
memo(forwardRef(...))) — out of scope, explicitly documented and integration-tested as limitation. A future fix requires a policy decision on which level wins the name. - Array-method callbacks (
arr.find) — accepted false-positive, now pinned by unit test. - Multi-arrow callback dedup (registry-primary only) — documented, legacy path pinned, rare enough not to block.
- No dedicated JS fixture for HOC patterns — low risk; the shared resolver and updated JS queries provide reasonable coverage via existing JS test suite.
Final verdict
production-ready
The core attribution fix is architecturally correct across all three touch points — resolver branch, registry-primary query, and legacy queries for both TS and JS. The arguments → call_expression → variable_declarator guard chain correctly excludes new_expression, bare statement-level calls, nested HOCs, and destructuring. Anchor discipline is right: inner-arrow placement in registry-primary ensures CALLS attribute to the named const, not to File. All previously documented trade-offs are now pinned by tests, not just comments — the four findings from the previous review round have been fully addressed. CI and Docker are green on head 861cbe6. The one remaining low-severity observation (implicit subtree matching for export const in registry-primary) is a documentation note, not a behavior gap. No blocking issues.
Summary
Closes the dominant remaining 0%-capture pattern from the original
issue #1166 audit: HOC-wrapped variable declarations like
const Button = React.forwardRef((props, ref) => { ... }),const Card = memo((props) => ...),const handleClick = useCallback(() => ..., []),const computed = useMemo(() => ..., []). Adds the missingtree-sitter patterns + extends
tsExtractFunctionNameso each wrappedarrow becomes a named
Functionnode with calls inside its bodycorrectly attributed to the const, not the file.
Motivation / context
Follow-up to PR #1175 (issue #1166), which fixed three HOF /
callback families (
Promise.all(.map(...)),useQuery({ queryFn: ... })pair-arrows, Zustand multi-action stores, JSX-as-call). Afterthat landed and Sourcerer-fe was re-indexed, the residual 0%-capture
audit identified one more dominant pattern: HOC-wrapped const
declarations. They share a common AST shape
— and pre-fix none matched any
@declaration.functionpattern ineither the registry-primary
query.tsor the legacytree-sitter-queries.ts. The legacy DAG’stsExtractFunctionNameonly walked
variable_declaratorandpairparents —argumentsparents fell through with
funcName = null, so calls inside thearrow walked up past the empty arrow scope and attributed to the
File.
Pattern coverage in real React UI codebases (Sourcerer-fe audit,
~525 source files, post-PR-#1175):
const X = ...declarationsReact.forwardRef/forwardRefReact.memo/memouseCallbackuseMemoAll ~296 declarations registered as anonymous arrows pre-fix. Every
function call inside their bodies (
cn(...),helper(...),imported callee invocations, render-time JSX) attributed to the
file, making
gitnexus_context/gitnexus_impactblind to theoutgoing call surface of every shadcn/Radix component, every
memoised React component, and every hook callback. The dominant UI
authoring style for modern React/TypeScript codebases.
This PR closes the gap.
Areas touched
gitnexus/(CLI / core / MCP server)gitnexus-web/(Vite / React UI).github/(workflows, actions)eval/or other toolingAGENTS.md,CLAUDE.md,.cursor/,llms.txt, etc.)Scope & constraints
In scope
gitnexus/src/core/ingestion/languages/typescript/query.ts(registry-primary path) covering
lexical_declaration/variable_declaration×arrow_function/function_expressioninside a
call_expression’sarguments. Anchored on the innerarrow (same anchor discipline as the existing
lexical_declarationand
pairpatterns from PR fix(typescript): capture missed CALLS edges from HOF callbacks and JSX #1175) so each wrapped arrow becomesits own caller anchor.
gitnexus/src/core/ingestion/tree-sitter-queries.ts(4 inTYPESCRIPT_QUERIES, 4 inJAVASCRIPT_QUERIES) — required forthe legacy Call-Resolution DAG and the CI parity gate.
arguments-parent branch intsExtractFunctionName(
gitnexus/src/core/ingestion/languages/typescript.ts) thatwalks
arguments → call_expression → variable_declaratorandreturns the const’s name. Mirrors the resolver fix at the function
-name layer so
findEnclosingFunctionId(parse-worker) andfindEnclosingFunction(call-processor) agree on attribution.(
gitnexus/test/integration/resolvers/typescript-hoc-wrapped.test.ts)(
gitnexus/test/fixtures/lang-resolution/typescript-hoc-wrapped/)exercising forwardRef / memo / useCallback / useMemo / observer /
debounce, with positive (named-Function + correct CALLS edge),
negative (no phantom Functions for unbound HOCs, no phantom
self-loops, no first-sibling-wins leakage), and cross-pollination
(two HOC-wrapped consts in the same module attribute independently)
assertions.
gitnexus/test/unit/call-attribution-issue-1166.test.tspinningthe legacy-DAG path: 6
tsExtractFunctionNameattribution tests(bare/member-expression callees, no first-sibling-wins, unbound
HOC negative) + 2
@definition.functioncapture tests (all wrappersExplicitly out of scope / not done here
const X = memo(forwardRef((p, r) => ...)). The deepest behaviour-arrow is two levels below the outervariable_declarator (its
argumentsparent’scall_expressionparent is itself an
argumentschild, not avariable_declarator),so neither the new patterns nor the new resolver branch match it.
nested.tsxdocuments the limitation and asserts no regression(no phantom edges introduced). A future fix would need a recursive
walk-up through
arguments → call_expressionchains and apolicy choice for which level wins the name.
const/let/var— barestatement-level calls (
useCallback(() => ..., [])discarded as aside-effect statement) intentionally remain anonymous and fall
back to File-level attribution. Covered by the
negative-bare-call.tsfixture and thedoes NOT name a bare statement-level HOC callunit test.gitnexus-web/src/components/GraphCanvas.tsxandRightPanel.tsx(React 19
RefObject<T | null>vsLegacyRef<T>mismatches). Theyexist on
mainalready and are orthogonal to this fix.language providers (Python, C#, Java, Go, …) define their own
function-name resolution and are unaffected.
Implementation notes
Pattern shape — anchor discipline
The four registry-primary patterns follow the same anchor-on-inner-
arrow discipline established by PR #1175 for the
lexical_declarationand
pairpatterns:(lexical_declaration (variable_declarator name: (identifier) @declaration.name value: (call_expression arguments: (arguments (arrow_function) @declaration.function))))@declaration.functionsits on the innerarrow_function, NOTthe outer
lexical_declarationorcall_expression. The arrow’srange matches its own
@scope.functionrange, sopass2AttachDeclarations.atPositionresolvesinnermostto thearrow’s own scope and
rangesEqual(anchor.range, innermost.range)triggers the auto-hoist that promotes the binding to the parent
scope (where the
const Xdeclaration lives). Without thisdiscipline, the def lands in the parent scope’s
ownedDefsandcalls inside the arrow walk up past the empty arrow scope, losing
attribution.
Trade-off — chained array-method false positive
The patterns intentionally don’t constrain the wrapping callee.
That accepts:
const Button = React.forwardRef((p, r) => ...)const Card = memo((p) => ...)const handleClick = useCallback(() => ..., [])const debouncedSearch = debounce((q) => ..., 250)…but also matches:
const x = arr.find((y) => predicate(y))— which would name the
.findcallback asx. The resultingFunction:xis mostly harmless:xis consumed as a value(
if (x) { ... }), never invoked as a function, so it accumulateszero incoming
CALLSedges. The one outgoing edgeFunction:x → predicateis a minor mis-attribution that could be filtered with aknown-array-method blocklist (
.map/.filter/.find/.reduce/
.forEach/.some/.every) — but (a) that blocklist needsmaintenance, (b) any user-defined fluent-API method with a callback
arg would still false-positive, and (c) the false-positive cost is
negligible vs. the much larger benefit of capturing forwardRef /
memo / useCallback for React UI codebases. See the long comment
block above the patterns in
gitnexus/src/core/ingestion/languages/typescript/query.tsfor thefull rationale.
Trade-off — multi-arrow arguments
const x = call(arrow1, arrow2)would emit two matches with thesame name
x(one per arrow).pass2AttachDeclarationspushes bothFunction:xdefs into their respective arrow scopes (each in itsown
ownedDefs) and hoists both bindings to the parent. Thedownstream registry’s qualified-name dedup collapses them via
(filePath, type, qualifiedName)— second wins. Acceptable;multi-arrow-callback APIs are rare (
new Promise(executor)is themain one and takes a single executor).
Resolver branch
tsExtractFunctionName’s third branch:Three guards (must be
arguments→call_expression→variable_declarator) keep this strictly scoped to HOC-wrappeddeclarations. Bare statement-level HOC calls (parent chain ends in
expression_statement/program) hit thevariable_declaratorguard and return null, so they correctly fall through anonymous.
Testing & verification
cd gitnexus && npx vitest run test/integration/resolvers/typescript-hoc-wrapped.test.ts→ 11/11 passed in 2.2s
cd gitnexus && npx vitest run test/unit/call-attribution-issue-1166.test.ts→ 26/26 passed (8 new HOC tests + 18 pre-existing) in 1.0s
cd gitnexus && npx vitest run test/integration/resolvers/typescript.test.ts test/integration/resolvers/typescript-hof-callbacks.test.ts test/integration/resolvers/typescript-jsx-as-call.test.ts test/integration/resolvers/typescript-hoc-wrapped.test.ts→ 266/266 passed on registry-primary (default) in ~38s
cd gitnexus && REGISTRY_PRIMARY_TYPESCRIPT=0 npx vitest run test/integration/resolvers/typescript.test.ts→ 236/236 passed on legacy DAG (CI parity gate) in ~45s
cd gitnexus && npx vitest run test/integration/resolvers/{typescript*,javascript,python,csharp,cpp,java,go,php,ruby,rust,dart,cobol,vue}*.test.ts→ 1693/1693 passed across all non-Kotlin/Swift languages in ~74s (Kotlin/Swift skipped — optional native bindings not installed locally per
AGENTS.md§ Gotchas)cd gitnexus && npx tsc --noEmit→ cleancd gitnexus && npx prettier --check <touched files>→ cleancd gitnexus && npx eslint <touched files>→ clean (5 pre-existingForbidden non-null assertionwarnings in the unit-test filematching its established style; no new errors)
Live re-index on Sourcerer-fe — confirmed empirical impact.
Sourcerer-fe@ commit9684e13, 525 source files, indexedtwice: once via published
gitnexus@1.6.3(pre-PR-fix(typescript): capture missed CALLS edges from HOF callbacks and JSX #1175,pre-this-PR baseline), once via this branch (post-PR-fix(typescript): capture missed CALLS edges from HOF callbacks and JSX #1175 +
post-this-PR; combined effect):
FunctionnodesFunctions with ≥ 1 outgoing CALLSFunction(correctly attributed)MethodFile(the smoking gun)ACCESSESedgesDEFINESedgesHeadline takeaways (combined effect of PR fix(typescript): capture missed CALLS edges from HOF callbacks and JSX #1175 + this PR):
Functionnodes unlocked — the entireReact-UI primitive surface that was previously invisible to
gitnexus_context/gitnexus_impact.CALLS share (Function-rooted) climbs from 88.2% → 93.7%.
previously fell through to the file as a fallback are now
attributed to the named function whose body they live in.
Concrete spot-check — shadcn/Radix UI primitives
Sample of new
Functionnodes insrc/components/ui/(noneregistered as
Functionpre-fix; the surroundingconst X = ...was a
Variableand the inner arrow was anonymous):And the
Button → cnoutgoing CALLS edge is now resolved — theexact edge our integration test (
forwardReftest intypescript-hoc-wrapped.test.ts) asserts post-fix and which thepre-fix
gitnexus@1.6.3binary does not produce.Micro-validation against this PR’s test fixture — same
typescript-hoc-wrapped/fixture (9 files, 7 HOC-wrappedconsts), copied to a tiny git repo and indexed via the pre-fix
gitnexus@1.6.3:Functionnodes for HOC constsFunctionFileButton → cnhandleClick → doStuffcomputed → doStuffThe pre-fix binary captures only 3 CALLS edges total on the
whole fixture, all File-rooted, and the targets are the
wrappers themselves (
debounce,wrap,observer) — everyButton → cn,Card → helper,handleClick → doStuff, etc.edge is absent pre-fix and present post-fix.
Risk & rollout
Risk: low-to-medium. The new query patterns and the resolver
branch are strictly additive — pre-existing patterns (plain
variable_declarator > arrow_function,pair > arrow_function,function declarations, methods) are untouched. The only behaviour
change is for arrows whose parent is
argumentsand whosearguments.parent.parentis avariable_declarator— pre-fix thosereturned null/anonymous, post-fix they return the const’s name.
Known false-positive surface: chained array-method declarations
(
const x = arr.find((y) => predicate(y))) get a phantomFunction:xwith one outgoing edgeFunction:x → predicate.Documented in
query.tsand accepted as an acceptable trade-off(see Implementation notes). No incoming-edge consequences.
Rollout:
npx gitnexus analyzeto pick up thenew
Functionnodes and edges. Existing graphs are forward-compatible (no schema change).
parity gate
(
.github/workflows/ci-scope-parity.yml) holds for TypeScript.Release notes (suggested entry):
Checklist
AGENTS.md/ overlay changes (kept out of this PR)