fix(callgraph): collect module-scope calls + object-literal arrow fns (closes #210)#221
fix(callgraph): collect module-scope calls + object-literal arrow fns (closes #210)#221Wolfvin wants to merge 1 commit into
Conversation
…closes #210) reference_count (rc) was severely undercounting cross-file calls due to two patterns silently dropped by the JS/TS backend parsers: 1. Module-scope calls — router.post(path, requirePermission('admin'), h) at the top level of a route file. The requirePermission('admin') call_expression was not inside any function declaration, so no edge was created. This caused middleware-factory functions to get rc=0 even when used in dozens of route files. 2. Arrow functions in object literals — const svc = { list: (ctx) => { ... } }. The variable_declarator value is an 'object' node (not arrow_function), so _parse_variable_declarator returned None and the arrow body calls were lost. This caused service-map modules to be invisible to the call graph. Root cause was in the PARSERS (js_backend_parser.py, ts_backend_parser.py), NOT in callgraph_engine.py as the issue body suspected. callgraph_engine.py is only used by the dataflow command; the rc field in search --mode symbol comes from parser -> edge_resolver.resolve_edges() -> registry. Fix: - Add _find_module_scope_calls pass that walks the AST and collects call_expression / new_expression nodes not inside any function body. Attributed to a synthetic <module> node (exported=True, node_type=module) so dead-code does not flag it. - Add _parse_object_literal_pair / _collect_object_literal_pairs that register arrow functions in object-literal pairs as <varName>.<key> nodes, so calls inside their bodies are properly attributed. - For TS parser: separate _find_object_literal_methods pass (the main walk skips export_statement children to avoid double-counting). - For JS parser: separate _collect_object_literal_pairs pass (same reason). Tests: tests/test_issue210_refcount_undercount.py — 9 tests covering both patterns for both TS and JS parsers, plus regression checks that regular function-declaration call attribution is preserved.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
|
PR ditutup — konflik dengan PR #219 yang sudah merge duluan (issue #210 sudah closed). Terima kasih untuk investigasi mendalamnya — 2 root cause yang ditemukan (module-scope calls + object-literal arrow functions) valid, tapi #219 sudah masuk lebih dulu dan cover root cause pertama (module-scope calls, inline arrow callback di argument) dengan approach synthetic source_id. Root cause KEDUA yang ditemukan (arrow function sebagai object-literal value, contoh Kalau mau lanjut kerjakan gap ini:
|


Closes #210
Patch Peta (delta struktural)
Files:
Endpoints: none
Schema: node baru: synthetic node (id=
<file>:0, fn=<module>, line=0, exported=True, node_type=module) hanya dibuat saat file punya module-scope calls. node baru: object_method nodes (fn=<varName>.<key>, node_type=object_method) untuk arrow functions di object-literal pairs.Konvensi:
rc/ref_countsekarang reliable untuk dua pattern yang sebelumnya silent-drop. Worker lain yang baca rc tidak perlu ubah — angka berubah jadi lebih akurat, bukan struktur field yang berubah.Klaim + Bukti
Root cause (terverifikasi via reproduksi sebelum fix)
Issue body menduga bug di
callgraph_engine.py(~line 1589/2055). Setelah investigasi:callgraph_engine.pyTIDAK dipakai untukrcfield disearch --mode symbol— itu hanya dipakai olehdataflowcommand. Pathrcyang benar:Bukti
callgraph_engine.pybukan path untukrc:Hanya
dataflow.pyyang pakaicallgraph_engine.Dua root cause di parsers
Root cause #1 — module-scope calls silent-dropped.
Parser hanya collect calls dari
decl["body_node"](function body). Calls di top-level file scope (di luar function apapun) tidak punya enclosing decl, jadi tidak pernah di-collect.Reproduksi sebelum fix (TS parser):
Hasil:
requirePermissiondapatrc=0meski dipanggil di file ini.Root cause #2 — arrow functions di object literals invisible.
_parse_variable_declaratorhanya handle value berupaarrow_function/function_expression/defineStorecall. Saat value adalahobjectnode (object literal), return None — arrow functions di dalam pair-nya tidak terdaftar sebagai node, sehingga calls di body-nya hilang.Reproduksi sebelum fix (TS parser):
Kombinasi root cause #2 + 1 site di function biasa →
hasPermissiondapatrc=1(hanya yang di function biasa tercatat), padahal ground truth 7.Bukti fix (reproduksi sesudah fix)
Case 1 — requirePermission (3 route files, module-scope):
Case 2 — hasPermission (object-literal arrow fns + 1 regular fn = 7 sites):
Sesuai Definition of Done #1 dan #2.
Test suite — 0 regresi baru
Filter yang relevan dengan issue (
callgraph,ref_count,parser,edge_resolver,search,dead_code,impact):Full suite (exclude pre-existing broken test files):
Baseline (sebelum fix, branch main): 17 failed, 2253 passed — jumlah sama, tidak ada failure baru. Pre-existing failures di-ignore per CONTEXT.md (test_integration.py workspace path issue, test_large_file_parsing.py tree-sitter Python binding segfault, test_lsp_server.py missing lsprotocol module).
Regression test baru — 9 tests, semua pass
Sesuai Definition of Done #4 dan #5.
Catatan Pendekatan
Issue body mengatakan: "Baca dulu
_parse_calls_treesitter(baris ~1589) dan_resolve_call(baris ~2055) dicallgraph_engine.pysebagai starting point — TAPI investigasi mandiri, jangan asumsikan lokasi bug ada di situ tanpa verifikasi." Investigasi menemukan bahwacallgraph_engine.pyBUKAN path untukrcfield — path yang benar adalahparsers/*.py→edge_resolver.py. Fix diterapkan di tempat yang benar, bukan di lokasi yang di-suggest oleh issue body.Fix sengaja diterapkan ke kedua parser (TS + JS) karena keduanya punya arsitektur paralel dan kemungkinan besar menderita bug yang sama. Test reproduksi dibuat untuk keduanya.
Breaking / Found-not-fixed
callgraph_engine.py(CallGraphBuilder._parse_calls_treesitter) juga punya bug serupa — calls di module scope tidak di-collect. Tapi karenacallgraph_engine.pyhanya dipakai olehdataflowcommand (bukanrc/search/dead-code/impact/trace), fix di sana di luar scope issue fix(callgraph): reference_count severely undercounts cross-file calls #210. Worker lain bisa buat issue terpisah kalau menemukandataflowunder-report.(req, res) => { hasPermission(...); }sebagai argumenrouter.post(...)) — calls di dalamnya sekarang ter-attribute ke<module>(karena arrow_function ada di FN_BODY_TYPES ancestor check), tapi arrow function-nya sendiri tidak terdaftar sebagai node bernama. Ini acceptable: count-nya benar, hanya granularity caller yang coarse. Kalau perlu finer attribution, issue terpisah.node_typefield baru (module,object_method) ditambahkan ke node dict. Downstream commands yang filter berdasarkan node_type mungkin perlu update kalau tidak expect value baru ini. Cek grep:node_typesudah dipakai di parser lain (e.g.pinia_store,class), jadi field-nya sudah dikenal — value baru saja yang ditambah.