Skip to content

fix(analysis): make dead-code detection correct and self-consistent - #1578

Merged
Shashankss1205 merged 2 commits into
mainfrom
fix/dead-code-analysis
Aug 5, 2026
Merged

Shashankss1205 merged 2 commits into
mainfrom
fix/dead-code-analysis

Conversation

@Shashankss1205

Copy link
Copy Markdown
Collaborator

Fixes #600 and #1559.

Full suite: 1101 passed, 19 skipped, no failures.

Dead-code detection had two implementations — CodeFinder.find_dead_code (CLI + MCP) and report_generator._section_dead_code (CGC_REPORT.md) — and each got something the other got wrong. The same repository produced different answers depending on which surface you asked.

#600 — module-level JS calls reported as dead

This one had sat since March with only a screenshot. The root cause:

OPTIONAL MATCH (caller:Function)-[:CALLS|HEURISTIC_CALLS]->(func)

A call at JS module level is not inside a function, so the writer persists it as a File-sourced edge — writer.py has an explicit caller_label == "File" branch producing (:File)-[:CALLS]->(:Function). Those edges were in the graph and simply not counted, so caller_count = 0 and the function was reported dead.

Python escapes this because its parser synthesizes a <module> frame (languages/python.py:211); javascript.py has no equivalent, which is why the bug is JS-specific.

The report generator already had this right with an anonymous caller. Now both do.

#1559 — four defects

① The entry-point filter hid real dead code. NOT func.name CONTAINS 'main' also excluded domain_check, remainder, maintain_index; same for the application / entry / entrypoint substring tests. Genuinely unused functions were never analyzed, with nothing to indicate it — the opposite failure from the false positives #1332 tracks. Now matched on the whole name via a shared _ENTRY_POINT_NAMES tuple.

② The CLI claimed classes were analyzed. The query matches (func:Function) only. I corrected the help text rather than implement class-level dead code — that needs its own design (a class with no incoming edges is a different question from an uncalled function).

analyze dead-code ignored its path argument. It was accepted, labelled "(not yet implemented)", and dropped — so running inside one repository reported dead code from every repository in the database. Now forwarded as the repo scope.

④ The two implementations disagreed. The report query had no name exclusions, so every dunder and test_* showed up as dead in CGC_REPORT.md while the CLI filtered them out. Both now share the same exclusion list and the same caller shape.

Tests

tests/unit/tools/test_dead_code_analysis.py — 8 tests, one per defect, each naming the failure it prevents. Reverting the source changes breaks collection, since the shared constant the report generator imports is itself part of the fix.

Not fixed here

#1332 (confidence scoring / ranking) is the UX layer on top of this and stays open. What this PR does is make the underlying set correct first — scoring a set that both hides real findings and disagrees with itself would have been building on sand.

🤖 Generated with Claude Code

…600, #1559)

Two implementations existed and each got something the other got wrong, so
the same repository gave different answers depending which you asked.

Module-level JS calls were reported as dead (#600)

`find_dead_code` counted only `(caller:Function)` callers. A call at JS
module level is not inside a function, so the writer persists it as a
File-sourced edge — writer.py has an explicit `caller_label == "File"`
branch producing `(:File)-[:CALLS]->(:Function)`. Those edges exist and
were simply not counted, so any JS function called only at module level
came back dead. Python escapes this because its parser synthesizes a
`<module>` frame; javascript.py has no equivalent. The caller is now
anonymous, matching what the report generator already did correctly.

The entry-point filter hid real dead code (#1559)

`NOT func.name CONTAINS 'main'` also excluded `domain_check`,
`remainder` and `maintain_index`; the same applied to the 'application',
'entry' and 'entrypoint' substring tests. Genuinely unused functions were
never analyzed and nothing said so — the opposite failure from the false
positives #1332 is about. Now matched on the whole name.

`analyze dead-code` ignored its path argument (#1559)

The argument was accepted, labelled "(not yet implemented)", and dropped,
so running inside one repository reported dead code from every repository
in the database. Now forwarded as the repo scope.

The report generator disagreed with the CLI (#1559)

Its query had no name exclusions at all, so every dunder and `test_*`
appeared as dead in CGC_REPORT.md. Both now share `_ENTRY_POINT_NAMES` so
they cannot drift apart again.

The CLI help claimed classes were analyzed (#1559)

The query matches `(func:Function)` only. Corrected the help rather than
implement class-level dead code, which needs its own design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
code-graph-context-pack Ready Ready Preview Aug 5, 2026 5:17pm

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Hi! 👋 Join our CodeGraphContext Discord channel to collaborate: https://discord.gg/dR4QY32uYQ

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Code Graph Analysis

fix(analysis): make dead-code detection correct and self-consistent (#1578)

📊 Interactive Visualization

View the blast radius graph: PR Reviewer Dashboard

📦 Artifacts

The graph JSON has been uploaded as a build artifact: pr-code-graph-1578


Generated by CodeGraphContext using FalkorDB Lite

@Shashankss1205
Shashankss1205 merged commit 1aa7e08 into main Aug 5, 2026
18 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog tasks to Done in CGC Progress Board Aug 5, 2026
@Shashankss1205
Shashankss1205 deleted the fix/dead-code-analysis branch August 5, 2026 17:24
Shashankss1205 added a commit that referenced this pull request Aug 8, 2026
Ten PRs since 0.5.6 — mostly parser correctness, all with regression tests.

Parsers
- Ruby nested classes no longer delete their parent; method parameters
  extracted at all (#1523, #1527, #1581)
- C/C++ functions returning a pointer or reference are extracted, and
  registered in pre_scan so they resolve as call targets (#1524, #1582)
- C# fields and locals parsed, restoring receiver-type inference for
  `var x = new T(); x.M();` (#1525, #1584)
- JavaScript destructured, array and rest parameters extracted, recording
  the binding names rather than a placeholder (#1527, #1591)

Queries and analysis
- Five defects that returned wrong answers without erroring: inflated
  repository stats, misattributed callers, discarded graph_name,
  count(*) after OPTIONAL MATCH, an ignored tool argument (#1577)
- Dead-code detection made correct and self-consistent, including the
  JS module-level false positive (#600, #1559, #1578)

Indexing
- add_package_to_graph on a flat module no longer indexes the whole
  virtualenv (#1528, #1586)
- SCIP job progress no longer exceeds 100% (#1535, #1583)

Docs and tests
- Database Options table columns realigned (#1590)
- MCP tool definition contract validation (#1580)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

In JS, direct function call in a file puts it as dead code, which is somewhat misleading

1 participant