Skip to content

fix(dead-code): add same-file-usage collectors for 7 languages (closes #220)#227

Merged
Wolfvin merged 1 commit into
mainfrom
fix/issue-220-deadcode-same-file-usage-multi-lang
Jul 12, 2026
Merged

fix(dead-code): add same-file-usage collectors for 7 languages (closes #220)#227
Wolfvin merged 1 commit into
mainfrom
fix/issue-220-deadcode-same-file-usage-multi-lang

Conversation

@Wolfvin

@Wolfvin Wolfvin commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Closes #220

Patch Peta (delta struktural)

Files:

  • scripts/deadcode_engine.py — add 6 same-file-usage collector functions (_collect_rust/go/java/php/ruby/c_same_file_usages) for 7 languages (Rust, Go, Java, PHP, Ruby, C, C++); wire into detect_dead_code() main loop; modify _detect_dead_from_registry() to accept+check same_file_usages param; add Counter import + 6 language keyword sets.
  • tests/test_deadcode_engine.py — add TestIssue220SameFileUsageCollectors (7 tests, one per language) + TestIssue220DetectDeadFromRegistry (2 tests for exemption logic).
  • docs/design/0220-same-file-usage-multi-lang.md — design doc documenting root cause, fix approach, 4 alternatives considered + rejected, and the count>=2 threshold rationale.

Endpoints: none
Schema: none (graph_nodes/graph_edges unchanged; same_file_usages is an in-memory dict passed between functions)
Konvensi: same_file_usages dict sekarang di-populate untuk 7 bahasa non-Python (Rust, Go, Java, PHP, Ruby, C, C++). Collector menggunakan Counter dengan threshold count>=2 — simbol yang hanya muncul di definisinya sendiri (count==1) TIDAK di-exempt, sehingga tetap bisa di-flag sebagai dead. Ini berbeda dari _collect_py_same_file_usages() yang menggunakan broad Set (count includes definition). Dev berikutnya yang menambah collector untuk bahasa baru WAJIB pakai pattern Counter + count>=2, BUKAN broad Set.

Klaim + Bukti

Root cause identified and documented. Issue #220 says root cause di _detect_unused_exports() tidak punya same_file_usages untuk non-Python. Investigasi mandiri menemukan DUA code paths yang terdampak:

  1. _detect_unused_exports() (line ~1750) — sudah check same_file_usages (line ~1873), tapi only Python yang populate. Fix: add collectors → automatically benefits.
  2. _detect_dead_from_registry() (line ~1904) — TIDAK check same_file_usages sama sekali. Ini path yang produce registry_dead findings (yang di-reported di issue). Fix: add same_file_usages parameter + exemption check.

Reproduction confirmed BEFORE fix (Rust fixture mirroring rekening-dev-mode/regrets/verify.rs):

$ codelens audit /home/z/my-project/work/repro220 --check dead-code --format json | jq '.r[0].stats'
{
  "total_dead_code": 5,
  "by_category": {"registry_dead": 5}
}
Findings: RED, GREEN, RESET (false positives — used 10+ times in-file), verify_all, genuinely_unused

Reproduction confirmed AFTER fix (same fixture, same command):

$ codelens audit /home/z/my-project/work/repro220 --check dead-code --format json | jq '.r[0].stats'
{
  "total_dead_code": 2,
  "by_category": {"registry_dead": 2}
}
Findings: verify_all, genuinely_unused (both genuinely dead — only appear in their definitions)

RED, GREEN, RESET no longer flagged (exempted by same_file_usages). 3 false positives eliminated.

DoD point 1 — const RED no longer appears in registry_dead/unused_exports:

$ codelens audit /home/z/my-project/work/repro220 --check dead-code --format json | jq '.r[0].results.registry_dead[].name'
"verify_all"
"genuinely_unused"

RED is NOT in the list. ✓

DoD point 2 — registry_dead count drops significantly: 5 → 2 (60% reduction on small fixture; issue reported 100 → expected ~20-30 on the real rekening-dev-mode codebase). ✓

DoD point 3 — no regression in Python + JS/TS:

$ python -m pytest tests/test_deadcode_engine.py --tb=short
23 passed in 0.13s

All existing Python/JS dead-code tests pass. JS/TS local_export heuristic not touched (per constraint). Python _collect_py_same_file_usages not touched (per constraint — only added new collectors for non-Python). ✓

DoD point 4 — min 1 unit test per language (7 total):

$ python -m pytest tests/test_deadcode_engine.py::TestIssue220SameFileUsageCollectors --tb=short
7 passed in 0.04s

Tests: test_rust_collector_exempts_used_consts_not_unused_fns, test_go_collector_..., test_java_..., test_php_..., test_ruby_..., test_c_..., test_cpp_... — each verifies collector correctly puts used symbols (count>=2) in the set and excludes definition-only symbols (count==1). ✓

Additional: _detect_dead_from_registry exemption logic tests:

$ python -m pytest tests/test_deadcode_engine.py::TestIssue220DetectDeadFromRegistry --tb=short
2 passed in 0.05s

Test 1: RED in same_file_usages → NOT flagged; genuinely_unused NOT in same_file_usages → flagged.
Test 2: Without same_file_usages (pre-fix behavior) → RED IS flagged (confirms pre-fix bug).

Full test suite — no new regressions:

$ python -m pytest tests/ --ignore=tests/test_integration.py --ignore=tests/test_lsp_server.py --ignore=tests/test_rule_engine.py --ignore=tests/test_rule_matcher.py --ignore=tests/test_rule_pattern_parser.py --ignore=tests/test_rule_test.py --ignore=tests/test_rule_validate.py --ignore=tests/test_semantic_engine.py --ignore=tests/test_large_file_parsing.py --tb=no
17 failed, 2145 passed, 96 skipped in 38.82s

Same 17 pre-existing failures as main (test_doctor, test_confidence, test_formatters_phase2, test_node_types — all unrelated to dead-code). 2145 passed = 2136 baseline + 9 new tests. ✓

Pre-flight checks passed:

Catatan Pendekatan

Issue constraint: "Jangan ubah _detect_unused_exports() inti — cukup tambah collector baru per bahasa". Respected — _detect_unused_exports() core not changed (it already had the same_file_usages check at line ~1873, so it automatically benefits from the new collectors).

Design decision: use collections.Counter with count >= 2 threshold instead of broad Set (like Python). Rationale: a symbol's own definition (e.g. const RED or fn foo) causes the name to appear at least once. Without the threshold, every symbol would exempt itself and no dead code would ever be flagged. With count >= 2, a symbol must appear in its definition PLUS at least one actual usage to be exempted. This differs from Python's broad Set approach but is necessary for the registry_dead path to preserve dead-code detection capability. See design doc for 4 alternatives considered.

Breaking / Found-not-fixed

Found, not fixed (Tier 2):

  1. 7 remaining languages (Lua, Elixir, Nim, C#, Swift, Scala, Dart, Shell) still don't have same_file_usages collectors. Issue says they can follow in a separate PR. Scope kept to 7 most common languages (Rust, Go, Java, PHP, Ruby, C, C++) per issue's "Tujuan" section.

  2. _collect_py_same_file_usages() still uses broad Set (count includes definition) — inconsistent with the new collectors' count>=2 approach. This means Python's unused_exports path is more permissive than non-Python. Not changed per issue constraint "Ikuti pattern _collect_py_same_file_usages() sebagai referensi". Could be aligned in a follow-up.

  3. _collect_name_references() for PHP has a pre-existing bug: (?<!\$)\b(\w+)\s*\( matches function DEFINITIONS (function genuinelyUnused()) as "calls", adding the name to all_imports. This causes genuinely-unused PHP methods to not be flagged by _detect_unused_exports (they appear "imported"). NOT caused by my change — pre-existing. Out of scope for fix(dead-code): same-file-usage tracking missing for 14 languages, causes false-positive registry_dead #220.

  4. _collect_rust_exports_imports() does NOT register pub const / pub static as exports — only pub fn/struct/enum/trait/type. So Rust consts are only checked via registry_dead (scan pipeline), not unused_exports (deadcode_engine direct). This is why the issue's example is registry_dead not unused_exports. Not changed — would affect _detect_unused_exports behavior for Rust.

  5. 17 pre-existing test failures on main (test_doctor, test_confidence, test_formatters_phase2, test_node_types) — all unrelated to dead-code, all present before this PR. Listed for awareness.

…#220)

Issue #220 reported severe false-positive registry_dead findings for
non-Python languages. In a real Rust codebase (rekening-dev-mode, 109
.rs files), 100 of 108 dead-code findings were registry_dead. Concrete
example: const RED used 10+ times in its own file (regrets/verify.rs)
was flagged as 'zero references' because consts are 'referenced' not
'called', so ref_count==0 even when heavily used.

Root cause: scripts/deadcode_engine.py only had a same-file-usage
collector for Python (_collect_py_same_file_usages). For the 14 other
languages, same_file_usages was always empty, causing:
1. _detect_unused_exports() to flag any export used only in-file
2. _detect_dead_from_registry() to flag any node with ref_count==0
   regardless of in-file usage (this path didn't check same_file_usages
   at all)

Fix:
- Add 6 collector functions for 7 languages (Rust, Go, Java, PHP, Ruby,
  C/C++ shared): _collect_<lang>_same_file_usages(). Each uses
  collections.Counter with count>=2 threshold to distinguish actual
  usage (definition + >=1 reference) from definition-only (count==1).
  This prevents a symbol's own definition from exempting it.
- Wire collectors into detect_dead_code() main loop at the same point
  as Python.
- Modify _detect_dead_from_registry() to accept same_file_usages and
  exempt symbols that appear in their own file's usage set.
- Don't change _detect_unused_exports() core (per issue constraint) —
  it already checks same_file_usages and automatically benefits from
  the new collectors.

Verified on Rust fixture mirroring the issue's rekening-dev-mode case:
- Before: 5 registry_dead (RED, GREEN, RESET, verify_all, genuinely_unused)
  — 3 false positives (consts used 10+ times in-file)
- After: 2 registry_dead (verify_all, genuinely_unused) — only genuinely
  dead symbols remain

9 new unit tests: 7 collector tests (one per language) + 2 registry-dead
exemption tests. All pass. No regressions (17 pre-existing failures
unchanged, 2145 passed = 2136 baseline + 9 new).
@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 2408e7b into main Jul 12, 2026
6 of 12 checks passed
@Wolfvin Wolfvin deleted the fix/issue-220-deadcode-same-file-usage-multi-lang branch July 12, 2026 04:50
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(dead-code): same-file-usage tracking missing for 14 languages, causes false-positive registry_dead

1 participant