How the Refactor Tool Detects Dead Code Across Communities: A Complete Technical Guide
The refactor tool detects dead code by analyzing the graph store representation of a repository, applying multi-phase filtering to exclude intentionally reachable symbols, and then verifying the absence of any CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges.
The code-review-graph repository provides a sophisticated dead-code detection system that goes beyond simple reachability analysis. This article breaks down exactly how the tool identifies unused symbols and generates community-aware refactoring suggestions, with direct references to the source implementation in code_review_graph/refactor.py.
How Dead Code Detection Works in Three Phases
The core detection logic resides in the find_dead_code function. The algorithm proceeds through three distinct phases before determining whether a symbol is truly dead.
Phase 1: Candidate Gathering
The tool begins by collecting all potential targets from the graph store. Any node of kind Function or Class becomes a candidate for dead-code analysis.
# From code_review_graph/refactor.py, lines 75-78
def find_dead_code(store, kind=None):
if kind:
candidates = store.get_nodes_by_kind(kind)
else:
candidates = store.get_nodes_by_kind("Function") + \
store.get_nodes_by_kind("Class")
This query returns every symbol defined in the codebase, regardless of whether it's actually used.
Phase 2: Filtering Out Known-Alive Symbols
The tool applies ten distinct filters to remove symbols that are intentionally reachable, even without explicit caller edges. These safety filters prevent false positives on framework code, test infrastructure, and language constructs.
| Filter | Purpose | Implementation |
|---|---|---|
| Test files | Exclude *_test.py, test_*.py, conftest.py |
_is_test_file (lines 56-84) |
| Dunder methods | Preserve __init__, __repr__, __str__, etc. |
Pattern matching (lines 90-104) |
| Constructors | Detect new ClassName() invocations |
Constructor edge analysis (lines 115-124) |
| Mock/stub variables | Skip variables matching _MOCK_NAME_RE |
Regex pattern _MOCK_NAME_RE (lines 129-138) |
| Framework base classes | Exclude subclasses of Django, Flask, FastAPI bases | _FRAMEWORK_BASE_CLASSES (lines 144-155) |
| CDK/IaC classes | Preserve classes with CDK suffixes | _CDK_CLASS_SUFFIXES (lines 166-176) |
| Type annotations | Keep types referenced in signatures | _collect_type_referenced_names (lines 184-199) |
| Entry points | Detect main(), cli(), @click.command(), etc. |
_is_entry_point (lines 206-226) |
| Framework decorators | Preserve @property, @abstractmethod, @staticmethod |
_has_framework_decorator (lines 232-254) |
| Abstract overrides & dataclasses | Keep protocol implementations | Special-case handling (lines 258-306) |
Each filter is implemented as an early-exit check. If any filter matches, the symbol is immediately marked as alive and removed from further consideration.
Phase 3: Final Deadness Verification
After filtering, remaining candidates undergo strict reachability verification. The tool checks for five edge types that indicate usage:
- CALLS: Another function invokes this symbol
- TESTED_BY: A test case exercises this symbol
- IMPORTS_FROM: Another module imports from this symbol
- REFERENCES: Static references exist without direct calls
- INHERITS: Class inheritance relationships
# Simplified logic from lines 374-418, 420-463, 465-511
def _is_actually_dead(node, store):
# Check all usage edge types
if store.get_edges_from(node.qn, "CALLS"): return False
if store.get_edges_from(node.qn, "TESTED_BY"): return False
if store.get_edges_from(node.qn, "IMPORTS_FROM"): return False
if store.get_edges_from(node.qn, "REFERENCES"): return False
if store.get_edges_from(node.qn, "INHERITS"): return False
# For classes: check if any member has callers
if node.kind == "Class":
for member in node.members:
if store.get_edges_from(member.qn, "CALLS"):
return False
# For methods: polymorphic dispatch check
if node.kind == "Function" and node.parent_class:
base_callers = _find_base_class_callers(node, store)
if base_callers:
return False
return True
The polymorphic dispatch check is particularly important: if a method overrides a base class implementation, the tool verifies whether the base method has callers, since those calls may dynamically dispatch to the override.
Community-Aware Refactoring: Moving Beyond Dead Code Detection
The dead-code detection described above is community-agnostic—it operates purely on graph connectivity. The community-aware features emerge in suggest_refactorings, which extends the analysis to detect misplaced code.
How Cross-Community Detection Works
The suggest_refactorings function (lines 631-692, 702-726, 732-754) implements a two-pass approach:
- Collect dead symbols using
find_dead_codedescribed above - Analyze live symbols for community misalignment
# From code_review_graph/refactor.py, lines 631-692
def suggest_refactorings(store):
suggestions = []
# Pass 1: Dead code → remove suggestions
dead = find_dead_code(store)
for symbol in dead:
suggestions.append({
"type": "remove",
"symbol": symbol["qualified_name"],
"description": f"Remove unused {symbol['kind']} {symbol['qualified_name']}"
})
# Pass 2: Community misalignment → move suggestions
communities = store.get_communities_list() # lines 702-726
node_to_community = _build_community_map(store) # lines 732-754
for func in store.get_nodes_by_kind("Function"):
f_community = node_to_community.get(func.qn)
caller_communities = _get_caller_communities(func, store)
# All callers belong to ONE different community
if len(caller_communities) == 1 and \
f_community not in caller_communities:
target_community = list(caller_communities)[0]
suggestions.append({
"type": "move",
"symbol": func.qn,
"from_community": f_community,
"to_community": target_community,
"description": f"Move {func.qn} from {f_community} to {target_community}"
})
return suggestions
A move suggestion is generated when:
- The function has at least one caller (it's not dead)
- All callers reside in a single community
- That community differs from the function's current community
This pattern indicates a cohesion violation: the implementation lives in one logical grouping but all its consumers live elsewhere.
Practical Usage Examples
Finding Dead Code in a Python Project
from code_review_graph.graph import GraphStore
from code_review_graph.refactor import find_dead_code
store = GraphStore()
store.load_from_path("./my-project")
dead = find_dead_code(store)
for symbol in dead:
print(f"{symbol['kind']} {symbol['qualified_name']} is dead")
# Example output:
# Function my_project.utils.legacy_parser is dead
# Class my_project.models.DeprecatedSchema is dead
Generating Community-Aware Refactoring Suggestions
from code_review_graph.refactor import suggest_refactorings
suggestions = suggest_refactorings(store)
for s in suggestions:
print(f"{s['type'].capitalize()}: {s['description']}")
# Example output:
# Remove: Remove unused Function my_project.old_api.v1_handler
# Move: Move my_project.auth.helpers.token_decoder from auth to api_gateway
The move suggestion in this example indicates that token_decoder is defined in the auth community but only called from api_gateway—a clear signal for reorganization.
Preview and Apply Refactorings Safely
from code_review_graph.refactor import rename_preview, apply_refactor
import pathlib
# Preview any refactoring type (not just renames)
preview = rename_preview(store, "oldFunc", "newFunc")
if not preview:
print("No changes needed or symbol not found")
exit()
# Always dry-run first
dry_result = apply_refactor(
preview["refactor_id"],
pathlib.Path("."),
dry_run=True
)
for filepath, diff in dry_result["diffs"].items():
print(f"\n=== {filepath} ===")
print(diff)
# Apply only after review
real_result = apply_refactor(preview["refactor_id"], pathlib.Path("."))
print(f"Applied {real_result['edits_applied']} edits across {real_result['files_modified']} files")
Key Implementation Files
| File | Role in Dead Code Detection |
|---|---|
code_review_graph/refactor.py |
Core implementation: find_dead_code, suggest_refactorings, preview/apply workflow |
code_review_graph/flows.py |
Entry-point detection helpers: _has_framework_decorator, _matches_entry_name |
code_review_graph/communities.py |
Community definitions and member lookups for move suggestions |
tests/test_refactor.py |
Unit tests for dead-code detection and preview-apply cycle |
tests/test_python_reachability.py |
Python-specific dead-code test cases |
Summary
- Dead code detection operates in three phases: candidate gathering, alive-symbol filtering via ten safety checks, and final edge-based verification
- Community-aware analysis extends dead-code detection to identify symbols that exist in the wrong logical grouping, generating move suggestions when all callers belong to a different community
- Framework-aware filters prevent false positives on test code, decorators, entry points, and language constructs like
__init__and abstract methods - Polymorphic dispatch handling ensures method overrides are not incorrectly flagged when base-class callers exist
Frequently Asked Questions
How does the tool avoid false positives on dead code detection?
The tool implements ten distinct safety filters in code_review_graph/refactor.py lines 56-306. These filters recognize test files, dunder methods, constructors, framework decorators, entry points, and type annotations as intentionally reachable patterns. Only symbols passing all filters undergo the final edge-based deadness check.
What edge types indicate a symbol is alive?
The verification phase checks for five edge types: CALLS (direct invocation), TESTED_BY (test coverage), IMPORTS_FROM (module imports), REFERENCES (static references), and INHERITS (class inheritance). For classes, member function callers are also verified. For methods, base-class callers are checked to handle polymorphic dispatch.
When does the tool suggest moving code versus removing it?
Remove suggestions apply to symbols with zero incoming edges after all filters. Move suggestions apply to symbols with callers that all reside in a single different community—defined by the communities table in the graph store. This distinction helps teams reorganize monorepos while preserving functionality.
Can the dead code detection handle dynamic Python features?
Yes, though with limitations. The polymorphic dispatch check (lines 465-511) handles method overrides by tracing to base-class implementations. However, highly dynamic patterns like getattr dispatch, eval usage, or runtime module injection may escape detection. The tool prioritizes soundness over completeness—it may miss some dead code but rarely flags live code as dead.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →