How the Refactor Tool in code-review-graph Detects Dead Code and Suggests Renames
The refactor tool in code-review-graph detects dead code by traversing the graph database in code_review_graph/refactor.py, applying multi-layered heuristics to filter out entry points and framework-managed symbols before flagging functions and classes with no callers, importers, or test references.
The code-review-graph repository implements a sophisticated static analysis engine that builds a comprehensive graph of code relationships. The dead-code detection logic lives primarily in the find_dead_code function within code_review_graph/refactor.py, which analyzes this graph to identify symbols that can be safely removed or renamed. By combining entry-point filtering, plausible-caller heuristics, and framework-aware exclusions, the tool minimizes false positives while surfacing genuinely unused code.
Entry-Point and Test File Filtering
Before analyzing call relationships, the tool eliminates obvious entry points that are intentionally invoked by external systems rather than internal code.
The helper _is_entry_point (lines 185‑197) filters functions bearing framework decorators or conventional entry names like main or handle_*. Similarly, _is_test_file (lines 208‑110) identifies and excludes test nodes and files matching common test-file patterns, ensuring that test-only usage does not mask production dead code.
Context Gathering from the Graph Database
The analyzer enriches its dead-code detection by gathering three critical contexts from the GraphStore:
- Type-referenced class names – The
_collect_type_referenced_namesfunction (lines 229‑238) scans function signatures for capitalized identifiers, preserving classes that appear only in type annotations. - Class hierarchy – The tool builds a map of
class → [base names]fromINHERITSedges (lines 279‑287) to understand inheritance relationships. - Import graph – The
importer_filesstructure (lines 288‑296) tracks which files import which symbols, enabling cross-file reachability analysis.
The Plausible-Caller Heuristic
Bare-name CALLS edges in the graph are inherently ambiguous. The _is_plausible_caller helper (lines 306‑346) resolves this by validating caller relationships against three criteria:
- The edge originates from the same file as the target symbol.
- The symbol name is globally unique, eliminating ambiguity.
- The importing file’s import path plausibly points to the target file, accounting for 2-hop re-exports and monorepo package alias heuristics.
Only edges satisfying one of these conditions are treated as genuine callers during dead-code analysis.
Excluding Framework-Managed and Special Symbols
A comprehensive set of early-return checks eliminates symbols that are intentionally uncalled but still required by frameworks or language features:
- Dunder methods and constructors – Lines 360‑368 skip
__init__,__repr__, and ambient.d.tsdeclarations. - Mock and stub variables – The
_MOCK_NAME_REpattern (lines 69‑75) ignores test-specific variables. - Framework-managed classes – Detection via
_FRAMEWORK_BASE_CLASSES(lines 25‑34) and validation at lines 389‑393 handles Django models, Pydantic classes, and similar framework entries. - Infrastructure-as-Code classes – The
_CDK_CLASS_SUFFIXESlist (lines 36‑38) preserves CDK constructs. - Decorator-driven indirect invocation – Lines 424‑440 recognize
@property,@abstractmethod, and similar decorators that imply external invocation patterns. - Abstract-method overrides – Lines 445‑471 detect methods overriding abstract base classes.
Core Dead-Code Decision Algorithm
For each remaining candidate symbol, find_dead_code aggregates five categories of graph edges to determine liveness:
- CALLS edges (
has_callers) – Both qualified and plausible bare-name callers (lines 474‑500). - TESTED_BY edges (
has_test_refs) – Test coverage relationships (lines 498‑525). - IMPORTS_FROM edges (
has_importers) – Cross-file imports (lines 529‑531). - REFERENCES edges (
has_references) – Values stored in data structures (line 531). - INHERITS edges (
has_subclasses) – Subclass relationships (line 532).
If none of these signals exist, the symbol is flagged as dead. For classes, the tool performs an additional check to ensure no members are called (lines 534‑549). Polymorphic dispatch is also considered: methods overriding abstract bases are kept alive if the base method has callers (lines 558‑580).
Finally, dead symbols are assembled into records containing the name, qualified name, kind, location, line number, and language (lines 882‑898).
From Detection to Rename Suggestions
While find_dead_code identifies unused symbols, the broader refactor tool leverages these findings through the suggest_refactorings pipeline. This downstream processor consumes the dead symbol list and generates specific recommendations, which may include renaming symbols to follow deprecation patterns or removing them entirely. The graph-based approach ensures that suggested renames account for all import relationships and call sites, preventing breaking changes in dependent code.
from code_review_graph.graph import GraphStore
from code_review_graph.refactor import find_dead_code
# Build a GraphStore from your repository
store = GraphStore.from_path("/path/to/repo")
# Detect dead code across the whole project
dead_symbols = find_dead_code(store)
for sym in dead_symbols:
print(f"{sym['kind']} {sym['qualified_name']} → {sym['file_path']}:{sym['line']}")
Summary
- The refactor tool operates in
code_review_graph/refactor.py, using thefind_dead_codefunction to traverse aGraphStoreof code relationships. - Entry-point filtering removes framework handlers, conventional names like
main, and test files before analysis begins. - The plausible-caller heuristic validates bare-name calls by checking same-file origin, global uniqueness, or import-path compatibility.
- Framework-aware exclusions prevent false positives for Django models, Pydantic classes, CDK constructs, and decorator-managed methods.
- Dead-code detection requires zero incoming CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges, with special handling for class members and polymorphic dispatch.
- Findings feed into the
suggest_refactoringspipeline to generate removal or rename recommendations.
Frequently Asked Questions
How does the tool avoid false positives for framework-managed code?
The analyzer maintains allowlists of framework patterns. It detects Django and Pydantic classes via _FRAMEWORK_BASE_CLASSES (lines 25‑34), recognizes CDK constructs through _CDK_CLASS_SUFFIXES (lines 36‑38), and identifies decorator-driven invocation through patterns like @property and @abstractmethod (lines 424‑440).
What graph edges does the dead-code analyzer check?
The algorithm examines five edge types: CALLS for direct invocations, TESTED_BY for test coverage, IMPORTS_FROM for cross-file imports, REFERENCES for data structure usage, and INHERITS for subclass relationships. A symbol must have at least one of these edges to be considered alive.
How does the plausible-caller heuristic handle ambiguous bare-name calls?
The _is_plausible_caller function (lines 306‑346) resolves ambiguity by treating a call as valid only if it originates from the same file, targets a globally unique name, or matches import-path heuristics including re-export chains and monorepo aliases.
Can the tool detect dead classes as well as functions?
Yes. Class detection follows the same edge-analysis logic but adds a member-check phase (lines 534‑549) to ensure no class methods are called. Classes appearing only in type annotations are preserved via _collect_type_referenced_names (lines 229‑238).
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 →