How the Refactor Tool Detects Dead Code in code-review-graph
The refactor tool detects dead code by traversing the Code-Review-Graph engine's dependency graph and flagging symbols that lack CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges while filtering out framework entry points and intentionally alive constructs.
The dead code detection system in the code-review-graph repository analyzes static relationships within your codebase to identify removable symbols. Located in code_review_graph/refactor.py, the find_dead_code function implements a multi-stage heuristic that distinguishes truly unused code from symbols that appear unused but serve framework or architectural purposes. This analysis examines six distinct signal types to determine whether a function or class can be safely deleted.
Filtering Entry Points and Test Files
Before analyzing symbol usage, the tool eliminates obvious entry points and test infrastructure that should never be considered dead.
The _is_entry_point function (lines 185-197) filters functions bearing framework decorators or conventional entry names like main and handle_*. Simultaneously, _is_test_file (lines 208-110) identifies and excludes test nodes and files matching common test patterns, ensuring test utilities are not flagged as dead code.
Gathering Context from the Graph Structure
The algorithm builds contextual maps to make informed decisions about symbol reachability.
Type-referenced class names are collected by _collect_type_referenced_names (lines 229-238), scanning function signatures for capitalized identifiers to preserve classes used only in type annotations. The class hierarchy is mapped from INHERITS edges (lines 279-287), and the import graph is constructed via importer_files (lines 288-296) to support caller resolution heuristics.
The Plausible-Caller Heuristic
Bare-name CALLS edges present ambiguity because they lack qualified module paths. The _is_plausible_caller function (lines 306-346) validates these edges only when the call originates from the same file, the symbol name is globally unique, or the importing file's path plausibly points to the target through re-exports or monorepo package aliases.
Excluding Known-Alive Constructs
A comprehensive set of early-return checks prevents false positives for symbols that are intentionally not called directly but remain alive through framework contracts.
The tool skips dunder methods, constructors, and ambient .d.ts declarations (lines 360-368), along with mock and stub variables in test files identified by _MOCK_NAME_RE (lines 69-75). Framework-managed classes such as Django models and Pydantic schemas are detected via _FRAMEWORK_BASE_CLASSES and decorator checks (lines 25-34 and 389-393), while CDK/IaC classes are identified by suffix patterns in _CDK_CLASS_SUFFIXES (lines 36-38).
Additional protections cover decorated properties and abstract methods (lines 424-440) and abstract-method overrides (lines 445-471), ensuring these symbols are never flagged for removal.
Core Dead-Code Decision Logic
For each remaining candidate, find_dead_code aggregates five critical signal types from the graph edges:
- CALLS edges (
has_callers): Qualified and plausible bare-name callers (lines 474-500) - TESTED_BY edges (
has_test_refs): Test coverage references with bare-name fallbacks (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 are present, the symbol qualifies as dead code. For classes, an additional check ensures no member methods are called (lines 534-549). Polymorphic dispatch is handled by checking if overriding methods should be kept alive when their abstract base has callers (lines 558-580).
Confirmed dead symbols are recorded with metadata including name, qualified name, kind, file path, and line number (lines 882-898) before being returned to downstream pipelines like suggest_refactorings.
Practical Usage Example
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']}")
This example demonstrates the standard workflow: instantiate a GraphStore, execute find_dead_code, and process the returned dictionaries containing symbol metadata and locations.
Summary
- Entry-point filtering removes framework entry points, main functions, and test files before analysis begins.
- Graph context gathering collects type annotations, inheritance hierarchies, and import relationships to support accurate heuristics.
- Plausible-caller validation resolves ambiguous bare-name calls using file locality, uniqueness checks, and import path analysis.
- Known-alive filtering preserves dunder methods, framework-managed classes, abstract methods, and CDK/IaC constructs that appear uncalled but serve architectural purposes.
- Edge-based detection considers CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, and INHERITS signals to determine symbol liveness, with special handling for polymorphic dispatch.
Frequently Asked Questions
What specific signals indicate that code is alive rather than dead?
The refactor tool considers five primary edge types as liveness signals: CALLS edges from other functions, TESTED_BY edges indicating test coverage, IMPORTS_FROM edges showing cross-file imports, REFERENCES edges for values stored in data structures, and INHERITS edges for subclass relationships. Additionally, symbols are kept alive if they override abstract methods with active callers or if they match framework-managed patterns detected through decorators and base class inheritance.
How does the tool handle polymorphic dispatch and abstract methods?
According to the source code in refactor.py (lines 558-580), the detector checks whether an overriding method belongs to a class that inherits from an abstract base. If the abstract base method has callers, the concrete implementation is considered alive even if it has no direct callers, preserving polymorphic dispatch chains. Abstract method declarations themselves are filtered out earlier in the process (lines 424-440).
What framework-specific patterns does the dead code detector recognize?
The tool maintains lists of framework base classes (_FRAMEWORK_BASE_CLASSES, lines 25-34) for Django, Pydantic, and similar frameworks, plus CDK class suffixes (_CDK_CLASS_SUFFIXES, lines 36-38) for infrastructure-as-code patterns. It also detects framework-specific decorators and entry-point naming conventions through _is_entry_point and _has_framework_decorator to prevent false positives on framework-managed lifecycle methods.
Can I use this dead code detection on a non-Python repository?
While the code_review_graph repository primarily targets Python codebases, the graph-based analysis approach is language-agnostic in principle. The GraphStore class in code_review_graph/graph.py abstracts the underlying storage, and the dead code logic operates on graph edges rather than Python AST specifics. However, the current implementation includes Python-specific heuristics such as dunder method detection and Pythonic decorator handling in refactor.py, so adapting it for other languages would require extending the heuristics to recognize language-specific alive-code patterns.
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 →