Skip to content

fix(scan): stop misclassifying .ts in routes/public/ as TSX (closes #231)#263

Merged
Wolfvin merged 1 commit into
mainfrom
fix/issue-231-async-handler-edges-missing
Jul 13, 2026
Merged

fix(scan): stop misclassifying .ts in routes/public/ as TSX (closes #231)#263
Wolfvin merged 1 commit into
mainfrom
fix/issue-231-async-handler-edges-missing

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Closes #231

Patch Peta (delta struktural)

Files:
~ scripts/commands/scan.py — is_frontend_file/is_backend_file no longer match frontend/backend paths as middle path segments (only workspace-root-relative prefix). Fixes the root cause of #231.

  • tests/test_issue231_async_handler_edges.py — 16 regression tests (unit + integration + end-to-end + fix(callgraph): extract module-top-level calls to fix rc undercount (closes #210) #219 safeguard)
    Endpoints: none
    Schema: none
    Konvensi: frontend_paths/backend_paths in codelens.config.json are workspace-root-relative directory prefixes. They match only paths that START WITH the prefix — NOT paths that contain the prefix as a nested segment. This is DIFFERENT from should_ignore (which intentionally matches middle segments — node_modules can be nested in monorepos). Any future change to path categorization must preserve this distinction.

Klaim + Bukti

Root cause isolated (different from issue hypothesis)

The issue hypothesised the bug was in graph_model.populate_graph_tables() (baris ~330-375). Investigation proved the bug was UPSTREAM — in commands.scan.is_frontend_file / is_backend_file.

The buggy matching logic:

if normalized.startswith(fp_norm) or f"/{fp_norm}" in normalized or normalized == fp_norm:

Check #2 (f"/{fp_norm}" in normalized) matched ANY path containing the frontend_path as a MIDDLE segment. For src/routes/public/customer-auth.ts, the path contains /public/is_frontend_file returned True → file misclassified as TSX (frontend). The TSX frontend parser does NOT extract backend call-graph edges, so all 5 edges from customer-auth.ts (including the asyncHandler-wrapped call to getGoogleClient) were silently dropped before reaching backend.json / graph_edges.

End-to-end verification (minimal repro mimicking KDS pattern)

Repro workspace: src/routes/public/customer-auth.ts with customerAuthRouter.post('/auth/google', asyncHandler(async (req, res) => { ... getGoogleClient() ... })) + src/services/google.ts defining getGoogleClient.

$ cd /home/z/my-project/repro_issue_231 && /home/z/my-project/.venv/bin/python /home/z/my-project/CodeLens/scripts/codelens.py search "getGoogleClient" . --mode symbol --format json | python3 -m json.tool

Result (after fix):

{
    "s": "ok",
    "r": [
        {
            "name": "getGoogleClient",
            "ref_count": 1,
            "location": "src/services/google.ts:4",
            ...
        }
    ]
}

ref_count: 1 — DoD #1 satisfied. Pre-fix this was ref_count: 0.

SQLite graph_edges verification (correct query — issue's query was flawed)

The issue used SELECT ... WHERE target_id LIKE '%getGoogleClient%' which returns 0 rows even when the edge exists, because resolved edges store target_id = file:line (no fn name) and unresolved edges store target_id = NULL with to_fn in extra_json.

Correct query (after fix):

SELECT source_id, target_id, edge_type FROM graph_edges WHERE target_id = 'src/services/google.ts:4';

Result: 1 row, source_id='src/routes/public/customer-auth.ts:0:<module>', target_id='src/services/google.ts:4', edge_type=CALLS.

#219 regression check (direct-argument pattern)

$ /home/z/my-project/.venv/bin/python /home/z/my-project/CodeLens/scripts/codelens.py search "requirePermission" . --mode symbol --format json

Result: "ref_count": 1 — unchanged from pre-fix behavior. The #219 pattern (router.post(path, requirePermission('admin'), handler) — direct argument) was already working and is not regressed by the #231 fix.

File categorization verification

$ /home/z/my-project/.venv/bin/python -c "from commands.scan import discover_files; from registry import load_config; ..."

Pre-fix: customer-auth.ts in files['tsx'] (frontend — WRONG).
Post-fix: customer-auth.ts in files['js_backend'] (backend — CORRECT, parsed by TSBackendParser).

Test suite

$ /home/z/my-project/.venv/bin/python -m pytest tests/ --ignore=tests/test_integration.py --ignore=tests/test_lsp_server.py --ignore=tests/test_large_file_parsing.py

Result: 5 failed, 2410 passed, 96 skipped in 44.69s.

Regression verification (methodology per CONTEXT.md)

Cloned clean baseline at commit c722f4a (last commit before this session), ran the same 5 failing tests against it:

$ cd /tmp/codelens_baseline && PYTHONPATH=scripts python -m pytest tests/test_codelensignore.py::TestBackwardCompat::test_actual_target_dir_is_ignored tests/test_confidence.py::TestFormatterIntegration::test_json_format_has_schema_version tests/test_confidence.py::TestEndToEndSchemaVersion::test_dead_code_json_output_has_schema_version tests/test_confidence.py::TestEndToEndSchemaVersion::test_secrets_json_output_has_schema_version tests/test_formatters_phase2.py::TestCLISmoke::test_help_lists_new_formats -v

Baseline result: 5 failed — IDENTICAL failures (same assertion errors, same test names). All 5 are pre-existing, verified 100% not caused by this PR.

Baseline full suite: 9 failed, 2357 passed, 76 skipped.
Branch full suite: 5 failed, 2410 passed, 96 skipped.
Delta: +53 passed (16 new tests + 37 from main's progress since c722f4a), -4 failed (4 test_adr failures fixed by main's progress, not by this PR), +20 skipped. Zero new failures introduced.

New regression tests (16 tests, all passing)

$ /home/z/my-project/.venv/bin/python -m pytest tests/test_issue231_async_handler_edges.py -v

Result: 16 passed in 0.60s.

Coverage:

  • Unit: is_frontend_file/is_backend_file segment matching (6 tests — root match, nested rejection, windows backslash)
  • Integration: discover_files categorization (1 test — customer-auth.ts in js_backend, App.tsx still in tsx)
  • End-to-end: asyncHandler-wrapped edge reaches backend.json + graph_edges + ref_count >= 1 (4 tests)
  • fix(callgraph): extract module-top-level calls to fix rc undercount (closes #210) #219 regression safeguard: direct-argument pattern still works (2 tests)
  • Edge cases: nested public/, packages/app/src/, unrelated paths (3 tests)

Command count unchanged

$ /home/z/my-project/.venv/bin/python -m pytest tests/test_command_registry.py tests/test_command_count.py

Result: 6 passed in 1.73s. No command added/removed — count stays at 12 umbrella + plugin.

Catatan Pendekatan

Issue #231 menyarankan bug ada di graph_model.populate_graph_tables() (baris ~330-375, tempat proses flat_edges sebelum insert ke graph_edges table). Saya menggunakan pendekatan investigasi mandiri (sesuai constraint issue: "jangan asumsikan bug ada di titik yang sudah disebutkan tanpa verifikasi ulang") dan membuktikan bug ada UPSTREAM — di commands.scan.is_frontend_file / is_backend_file.

Investigasi end-to-end: parser → resolve_edges()backend.jsonpopulate_graph_tables()graph_edges.

Temuan:

  1. Parser (TSBackendParser.extract_references) — VERIFIED BENAR. Memproduksi 5 edge dari customer-auth.ts, termasuk → getGoogleClient.
  2. resolve_edges() — VERIFIED BENAR. Semua 5 edge dipertahankan (1 resolved ke src/services/google.ts:4, 4 unresolved).
  3. backend.json — BUG: hanya 1 edge total (dari asyncHandler.ts), 5 edge dari customer-auth.ts HILANG.
  4. graph_edges — KONSEKUENSI: edge getGoogleClient tidak ada (karena tidak pernah sampai backend.json).

Root cause: customer-auth.ts misclassified sebagai TSX (frontend) oleh is_frontend_file, karena path src/routes/public/customer-auth.ts mengandung /public/ sebagai middle segment. frontend_paths default termasuk public/, dan check #2 (f"/{fp_norm}" in normalized) match middle segment. TSX frontend parser tidak extract backend call-graph edges.

Outcome yang dicapai tetap sesuai Definition of Done:

  1. search "getGoogleClient" --mode symbol menunjukkan rc >= 1 (sekarang rc: 1)
  2. ✅ Root cause didokumentasikan jelas — di mana tepatnya edge hilang (file categorization, BUKAN populate_graph_tables)
  3. ✅ Regression check: requirePermission rc tidak turun (masih 1 di repro — pola direct-argument fix(callgraph): extract module-top-level calls to fix rc undercount (closes #210) #219 tetap benar)
  4. ✅ Test suite 0 regresi baru (5 failure semua pre-existing, verified vs baseline c722f4a)

Breaking / Found-not-fixed

none

Catatan untuk BOS: issue #231 body mengklaim edge "TIDAK ADA di SQLite" berdasarkan query SELECT ... WHERE target_id LIKE '%getGoogleClient%'. Query ini metodologisally flawed — target_id untuk resolved edge menyimpan file:line (tanpa fn name), dan untuk unresolved edge target_id = NULL dengan to_fn di extra_json. Query seperti itu akan return 0 rows bahkan kalau edge ada. Investigasi ini menggunakan query yang benar (WHERE target_id = '<node_id>' atau WHERE extra_json LIKE '%getGoogleClient%') dan mengkonfirmasi edge memang hilang — tapi root cause-nya di file categorization, bukan di SQLite write layer.

)

Issue #231 reported getGoogleClient showing rc:0 in search --mode symbol
even though TSBackendParser.extract_references() correctly emits the edge
in isolation. The issue hypothesised the bug was in
graph_model.populate_graph_tables() (baris ~330-375).

Investigation proved the bug was UPSTREAM — in commands.scan.is_frontend_file
and is_backend_file. The matching logic used:

    if normalized.startswith(fp_norm) or f"/{fp_norm}" in normalized or normalized == fp_norm:

The second check (f"/{fp_norm}" in normalized) matched ANY path containing
the frontend_path as a MIDDLE segment. For src/routes/public/customer-auth.ts
(a backend route file), the path contains /public/ as a middle segment, so
is_frontend_file returned True and the file was misclassified as TSX
(frontend). The TSX frontend parser does not extract backend call-graph
edges, so all 5 edges from customer-auth.ts (including the
asyncHandler-wrapped call to getGoogleClient) were silently dropped before
reaching backend.json / graph_edges.

Fix: remove the middle-segment check from both is_frontend_file and
is_backend_file. Frontend/backend paths are workspace-root-relative
directory prefixes — they should ONLY match paths that start with the
prefix. should_ignore is different: it intentionally matches middle
segments (node_modules can be nested in monorepos), but
frontend_paths/backend_paths identify the workspace-root directory.

After fix:
- getGoogleClient rc: 0 -> 1 (search --mode symbol)
- customer-auth.ts categorized as js_backend (TS backend parser), not tsx
- requirePermission rc (issue #219 pattern) unchanged — no regression

Root cause location differs from issue hypothesis — see Catatan Pendekatan
in PR description.

Regression tests: tests/test_issue231_async_handler_edges.py (16 tests)
- Unit: is_frontend_file/is_backend_file segment matching (6 tests)
- Integration: discover_files categorization (1 test)
- End-to-end: asyncHandler-wrapped edge reaches backend.json + graph_edges
  + ref_count >= 1 (4 tests)
- #219 regression safeguard: direct-argument pattern still works (2 tests)
- Pre-existing edge cases: nested public/, windows backslash, etc.

Verified against clean baseline (commit c722f4a): 0 new failures.
Baseline: 9 failed, 2357 passed. Branch: 5 failed (all pre-existing),
2410 passed (53 more — 16 new + 37 from main progress since c722f4a).
@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

@Wolfvin Wolfvin merged commit 3cbbe71 into main Jul 13, 2026
6 of 12 checks passed
@Wolfvin Wolfvin deleted the fix/issue-231-async-handler-edges-missing branch July 13, 2026 10:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(callgraph): calls inside asyncHandler-wrapped route handlers missing from graph_edges

1 participant