How the code-review-graph Refactoring Tool Detects Dead Code and Suggests Renames
The refactoring tool in tirth8205/code-review-graph detects dead code by exhaustively analyzing graph relationships—CALLS, IMPORTS_FROM, REFERENCES, and TESTED_BY edges—to identify symbols with no plausible callers, while rename suggestions are generated by collecting definition sites and all references into previewable, reversible edit operations.
The refactoring subsystem resides in code_review_graph/refactor.py and exposes two primary utilities: rename_preview for constructing safe rename operations and find_dead_code for identifying unused symbols. These functions query the underlying graph database defined in graph.py and leverage framework-detection helpers from flows.py to perform static analysis without executing the target codebase.
Generating Rename Previews
The rename workflow follows a preview-then-apply pattern that allows developers to inspect changes before modifying source files.
Locating Symbol References
When you invoke rename_preview(store, old_name, new_name), the function queries the graph using store.search_nodes to locate the target symbol. It collects the definition site, all CALLS edges, and all IMPORTS_FROM edges that reference the name. After deduplicating edits, it generates a unique preview ID, stores the operation set in a thread-safe dictionary, and returns a summary object containing the proposed file modifications.
# Suppose we have a GraphStore `store` already populated.
preview = rename_preview(store, old_name="oldFunc", new_name="newFunc")
if preview:
print("Preview ID:", preview["refactor_id"])
for e in preview["edits"]:
print(f'{e["file"]}:{e["line"]} – replace {e["old"]!r} with {e["new"]!r}')
Applying Refactors with Dry-Run Support
The apply_refactor function accepts the preview ID and a repository root path. It supports a dry_run mode that simulates changes without writing to disk, returning a list of files that would be modified along with unified diffs. When executed without dry-run mode, it applies the buffered edits to the working tree.
result = apply_refactor(preview["refactor_id"], repo_root=Path("."), dry_run=True)
print("Would modify:", result["would_modify"])
print("Diff for first file:\n", result["diffs"][result["would_modify"][0]])
# When ready:
apply_refactor(preview["refactor_id"], repo_root=Path("."))
Detecting Dead Code
The find_dead_code function implements a conservative, multi-stage filtering pipeline to identify potentially unused Function and Class nodes. The algorithm minimizes false positives by excluding symbols that are entry points, framework-managed, or implicitly referenced through inheritance.
Candidate Gathering and Exclusion Filters
The process begins by retrieving candidate nodes via store.get_nodes_by_kind (approximately line 245). The system applies successive filters to eliminate symbols that are intentionally reachable:
- Entry point filtering – The
_is_entry_pointhelper (lines 89-102) excludes functions matching patterns likemain,handle_*, or conventional CLI entry points. - Test artifact exclusion – The
_is_test_filecheck (lines 112-115) removes nodes residing in test directories or files. - Framework integration – Symbols managed by frameworks are preserved using:
_FRAMEWORK_BASE_CLASSESinheritance checks_CDK_CLASS_SUFFIXESpattern matching for CDK-style constructs_has_framework_decoratoranalysis implemented inflows.py(referenced around lines 998-1020)
- Language specials – Dunder methods (
__init__, etc.), constructors,.d.tstype declarations, and mock variables matching_MOCK_NAME_REare retained (lines 964-979).
Reference and Caller Analysis
After filtering, the tool constructs auxiliary data structures to reason about "plausible callers":
- Type annotation references –
_collect_type_referenced_names(lines 330-382) builds a set of class names used in type hints, which may indicate implicit usage. - Import graph construction – Lines 292-300 map module dependencies to track cross-module references.
The system queries CALLS edges targeting the qualified name. If none exist, it searches for bare-name calls and validates them through _is_plausible_caller (lines 788-805). For test coverage validation, it inspects TESTED_BY edges where the production node is the source (lines 806-822).
Inheritance and Polymorphic Dispatch
For class definitions, the analysis extends to INHERITS edges (lines 834-839) to ensure subclasses are considered active usage. If a class has no direct callers, the tool inspects whether any member methods are invoked (lines 846-862).
For method overrides that might be invoked via base-class references, the tool walks the inheritance hierarchy to verify if the parent method has CALLS edges (lines 871-892). This prevents flagging virtual methods as dead when they are executed through polymorphic dispatch.
Final Classification
A symbol is reported as dead only if it satisfies all of the following conditions (lines 894-910):
- No CALLS edges from plausible callers
- No TESTED_BY edges indicating test coverage
- No IMPORTS_FROM or REFERENCES edges (lines 842-845)
- No subclasses inheriting from the class (for classes)
- No called member methods (for classes)
When identified, suggest_refactorings transforms each result into a "remove" suggestion containing the symbol name, qualified name, file path, and line number.
dead_symbols = find_dead_code(store, kind="Function")
for d in dead_symbols:
print(f'Dead {d["kind"]} {d["qualified_name"]} in {d["relative_path"]}:{d["line"]}')
Summary
- The refactoring tool is implemented in
code_review_graph/refactor.pyand providesrename_preview,find_dead_code, andapply_refactorutilities. - Rename suggestions work by collecting definition sites and all referencing edges (CALLS, IMPORTS_FROM), storing them in a thread-safe preview buffer that can be inspected or applied atomically.
- Dead code detection uses a conservative multi-pass filter that excludes entry points, test files, framework-managed symbols, and language specials before analyzing CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, and INHERITS edges.
- The tool handles polymorphic dispatch by walking inheritance hierarchies to validate whether base-class methods have callers through subclass instances.
- All operations leverage the
GraphStoreinterface fromgraph.pyand framework detection helpers fromflows.pyto perform static analysis without code execution.
Frequently Asked Questions
How does the tool avoid false positives when flagging dead code?
The tool implements multiple conservative heuristics before marking code as dead. It excludes symbols that serve as entry points (like main or handle_*), reside in test files, inherit from framework base classes, or carry framework-specific decorators. It also preserves dunder methods, constructors, and type declaration files. By requiring the complete absence of CALLS, IMPORTS_FROM, REFERENCES, and TESTED_BY edges—while also checking for subclass usage and member method invocations—the algorithm minimizes false positives while maintaining sensitivity for truly unreachable code.
Can the refactoring tool rename symbols across multiple files?
Yes. The rename_preview function collects all reference sites including IMPORTS_FROM edges, which capture cross-module imports. When you apply the refactor, the tool generates edit operations for every file containing references to the old symbol name, ensuring consistent renaming across the entire codebase. The preview mechanism allows you to verify the scope of changes before committing them.
What happens if a method is only called through a parent class interface?
The dead code detector handles polymorphic calls by walking the inheritance hierarchy. When analyzing a method override (lines 871-892), it checks whether the corresponding base-class method has CALLS edges. If the parent method is invoked, the subclass override is considered live because it could be executed via dynamic dispatch, preventing the tool from incorrectly flagging virtual method implementations as unused.
Is it safe to run the dead code analysis in a CI/CD pipeline?
Yes, the analysis is read-only and operates entirely on the graph structure without modifying source files. The find_dead_code function queries the GraphStore without side effects, making it suitable for automated checks. However, you should review findings before acting on them, as the tool may flag intentionally unused symbols—such as plugin hooks or public API surfaces—if they lack the expected edge relationships in the analyzed graph.
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 →