Skip to content

fix(callgraph): register object-literal arrow fns as nodes (closes #222)#225

Merged
Wolfvin merged 1 commit into
mainfrom
fix/issue-222-object-literal-arrow-fns
Jul 12, 2026
Merged

fix(callgraph): register object-literal arrow fns as nodes (closes #222)#225
Wolfvin merged 1 commit into
mainfrom
fix/issue-222-object-literal-arrow-fns

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Closes #222

Patch Peta (delta struktural)

Files:

  • scripts/parsers/ts_backend_parser.py — tambah _find_object_literal_method_decls (walk AST, cari variable_declarator dengan object value, register pair dengan arrow/function value sebagai node .). Update _find_module_level_calls untuk skip pair dengan arrow/function value (hindari double-count).
  • scripts/parsers/js_backend_parser.py — tambah _collect_object_literal_method_calls (serupa, tapi collect calls inline konsisten dengan single-pass design JS parser). Update _find_module_level_calls dengan pair-skip yang sama.
  • tests/test_ts_backend_parser.py — 8 regression tests (TestTSBackendParserIssue222ObjectLiteralMethods).
  • tests/test_js_backend_parser.py — 8 regression tests (TestJSBackendParserIssue222ObjectLiteralMethods).

Endpoints: none
Schema: node baru: object_method nodes (fn=<varName>.<key>, node_type=object_method) untuk arrow functions di object-literal pairs. Format konsisten dengan node lain (<file>:<line> id, field sama seperti pinia_store type).
Konvensi: rc/ref_count untuk callee tidak berubah (calls tetap ter-extract, hanya caller-nya berubah dari <module> ke <varName>.<key>). trace/impact/search sekarang bisa resolve method by name. Tidak ada breaking change — downstream code yang baca rc tidak perlu ubah.

Klaim + Bukti

Verifikasi state main post-#219 (sebelum fix)

Issue body mengatakan: "Cek dulu behavior sekarang di main (post-#219) sebelum mulai — jangan asumsikan kode dari PR #221 yang di-reference di sini masih relevan tanpa verifikasi ulang terhadap state main terbaru."

Verifikasi via reproduksi script (/home/z/my-project/scripts/verify_issue222.py):

=== TS parser (main, sebelum fix) ===
  src/services/assignments.ts: 0 nodes, 3 edges
    edge: from='src/services/assignments.ts:0:<module>'  to_fn='hasPermission'
    edge: from='src/services/assignments.ts:0:<module>'  to_fn='hasPermission'
    edge: from='src/services/assignments.ts:0:<module>'  to_fn='hasPermission'
  hasPermission ref_count=3

Konfirmasi: PR #219 (_find_module_level_calls) SUDAH meng-cover calls di dalam object-literal arrow functions secara TIDAK LANGSUNG. Karena variable_declarator dengan object value tidak di-skip oleh module-level pass, walk masuk ke object literal dan extract calls dari arrow body, attribute ke <module>.

Jadi rc=3 SUDAH benar sebelum fix ini. Tapi issue #222 EKSPLISIT meminta untuk register arrow function sebagai node <varName>.<key> (bukan hanya collect calls-nya). Tujuan eksplisit: supaya trace/impact/search --mode symbol bisa resolve method by name.

Root cause detail

_parse_variable_declarator di kedua parser hanya handle value berupa:

  • arrow_function (e.g. const fn = () => {})
  • function_expression (e.g. const fn = function() {})
  • call_expression dengan function defineStore (Pinia store pattern)

Kalau value adalah object node (object literal), return None — arrow functions di dalam pair-nya tidak pernah diregister sebagai node.

Bukti fix (sesudah fix)

=== TS parser (after fix) ===
  src/services/assignments.ts: 3 nodes, 3 edges
    node: src/services/assignments.ts:4  fn='assignmentService.list'
    node: src/services/assignments.ts:8  fn='assignmentService.create'
    node: src/services/assignments.ts:12  fn='assignmentService.remove'
    edge: from='src/services/assignments.ts:4'  to_fn='hasPermission'
    edge: from='src/services/assignments.ts:8'  to_fn='hasPermission'
    edge: from='src/services/assignments.ts:12'  to_fn='hasPermission'
  hasPermission ref_count=3

=== JS parser (after fix) ===
  src/services/assignments.js: 3 nodes, 3 edges
    node: src/services/assignments.js:4  fn='assignmentService.list'
    node: src/services/assignments.js:8  fn='assignmentService.create'
    node: src/services/assignments.js:12  fn='assignmentService.remove'
    edge: from='src/services/assignments.js:4'  to_fn='hasPermission'
    edge: from='src/services/assignments.js:8'  to_fn='hasPermission'
    edge: from='src/services/assignments.js:12'  to_fn='hasPermission'
  hasPermission ref_count=3

Sesuai Definition of Done:

  1. ✅ Reproduksi kasus — call di dalam method ter-extract dengan benar (rc=3)
  2. search --mode symbol untuk fungsi yang dipanggil hanya lewat pattern ini menunjukkan rc yang benar (rc=3, bukan 0)
  3. ✅ Tidak ada regresi terhadap fix fix(callgraph): extract module-top-level calls to fix rc undercount (closes #210) #219 — test suite fix(callgraph): reference_count severely undercounts cross-file calls #210 tetap pass (test_ts_backend_parser.py TestTSBackendParserIssue210ModuleLevel, test_js_backend_parser.py TestIssue210ModuleLevelCalls)
  4. ✅ Minimal 2 unit test baru — 16 tests (8 TS + 8 JS)

Test suite — 0 regresi baru

Parser tests (existing + baru):

$ python -m pytest tests/test_js_backend_parser.py tests/test_ts_backend_parser.py -v
47 passed (31 existing + 16 baru)

Full suite (exclude pre-existing broken test files per CONTEXT.md):

$ python -m pytest tests/ --ignore=tests/test_integration.py --ignore=tests/test_lsp_server.py --ignore=tests/test_large_file_parsing.py
2276 passed, 102 skipped, 17 failed

Baseline (main sebelum fix): 17 failed, 2260 passed → 2276 passed setelah fix (naik 16 = test baru). Jumlah failure sama (17) — semua pre-existing (doctor/formatters/codelensignore/node_types — env-specific, bukan dari fix ini).

Checklist Laporan Worker

Catatan Pendekatan

Issue body mengatakan: "Supaya calls di dalam body-nya ter-collect dengan benar." Investigasi menemukan bahwa calls SUDAH ter-collect dengan benar (rc=3) berkat PR #219, TAPI arrow function-nya sendiri tidak terdaftar sebagai node. Fix ini fokus pada node registration (sesuai tujuan eksplisit issue body: "register sebagai node dengan nama <varName>.<key>"), bukan pada call extraction.

Approach: tambah pass terpisah untuk find object-literal method decls, lalu update _find_module_level_calls untuk skip pair dengan arrow/function value (hindari double-counting antara per-function pass yang baru dan module-level pass yang sudah ada dari #219).

Untuk JS parser, approach sedikit berbeda karena JS parser pakai single-pass iterative walk dengan keep_alive (bukan two-pass seperti TS parser). Method baru _collect_object_literal_method_calls collect calls inline (register node + collect body calls dalam satu operasi), konsisten dengan _parse_and_collect_calls pattern yang sudah ada.

Breaking / Found-not-fixed

  • Object literal yang TIDAK di-assign ke variable (e.g. return { list: () => {} } di dalam function body, atau { handler: () => {} } sebagai call argument) TIDAK terdaftar sebagai node — tidak ada stable var name. Calls di dalamnya tetap ter-extract via per-function pass (untuk yang di function body) atau module-level pass (untuk yang di module scope). Ini acceptable: trace/impact tidak bisa resolve by name, tapi rc tetap benar. Follow-up optional kalau ada use case spesifik.
  • Nested object literal (e.g. const svc = { api: { list: () => {} } }) — hanya outermost var name yang dipakai → svc.list (bukan svc.api.list). Ini keeps naming simple dan matches Pinia store convention. Kalau perlu nested naming, issue terpisah.
  • node_type field baru (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_type sudah dipakai di parser lain (e.g. pinia_store, class), jadi field-nya sudah dikenal — value baru saja yang ditambah.

Arrow functions assigned to object-literal properties (e.g.
`const svc = { list: (ctx) => { ... } }`) were invisible as function
nodes — `_parse_variable_declarator` only handles value berupa
arrow_function/function_expression/defineStore call, bukan object node.

Sebelum fix: calls di dalam arrow body sudah ter-extract oleh module-level
pass (PR #219, issue #210) dan di-attribute ke synthetic <module> node,
jadi ref_count callee sudah benar. Tapi arrow function-nya sendiri TIDAK
terdaftar sebagai node, sehingga trace/impact/search --mode symbol tidak
bisa resolve method by name.

Fix:
- TS parser: tambah _find_object_literal_method_decls yang walk AST,
  cari variable_declarator dengan object value, register setiap pair
  dengan arrow/function value sebagai node <varName>.<key>.
- JS parser: tambah _collect_object_literal_method_calls yang serupa
  tapi collect calls inline (konsisten dengan single-pass design JS parser).
- Update _find_module_level_calls di kedua parser untuk skip pair nodes
  dengan arrow/function value — hindari double-counting antara per-function
  pass (yang sekarang register method tsb) dan module-level pass.

Hasil:
- Arrow function terdaftar sebagai node <varName>.<key> (e.g.
  assignmentService.list, assignmentService.create, assignmentService.remove)
- Calls di dalam arrow body di-attribute ke method node id, bukan <module>
- ref_count callee tetap sama (tidak double-counted)
- trace/impact/search sekarang bisa resolve method by name

Tests: 16 regression tests (8 TS + 8 JS) — registration, call attribution,
multi-method, ref_count end-to-end, non-arrow pair handling, no double-count,
object literal in function body.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
5.9% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@Wolfvin Wolfvin merged commit cdcee88 into main Jul 12, 2026
5 of 12 checks passed
@Wolfvin Wolfvin deleted the fix/issue-222-object-literal-arrow-fns branch July 12, 2026 04:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(callgraph): arrow functions as object-literal values not tracked, undercounts ref_count

1 participant