How Refactoring Tools Detect Dead Code and Suggest Renames in code-review-graph
The refactoring subsystem in code-review-graph constructs a language-agnostic call graph, performs DFS-based reachability analysis from entry points to isolate unreachable definitions as dead code, and computes Levenshtein-distance similarity scores on identifiers to suggest context-aware renames that are validated against import edges before application.
The code-review-graph repository provides automated refactoring capabilities for multi-language codebases. Its refactoring tools detect dead code and suggest renames by analyzing a centralized graph representation that connects definitions, references, and imports across the entire repository.
Building the Language-Agnostic Call Graph
Dead-code detection and rename accuracy depend on a comprehensive graph structure built by the parsing subsystem.
Parsing and Graph Population
Language-specific parsers extract symbols and references from source files. The parser.py module orchestrates this process, while jedi_resolver.py handles Python-specific resolution using the Jedi library. These components populate graph.py, which stores nodes for functions, classes, and variables, along with edges representing calls, imports, and references.
Entry Point Identification
The find_dead_code function in code_review_graph/refactor.py identifies entry points as the starting nodes for reachability analysis. These include exported symbols, main functions, test suites, and declared public APIs. Any definition not reachable from these roots is a candidate for dead-code elimination.
Detecting Dead Code via Reachability Analysis
Once the graph is constructed, the system applies graph traversal algorithms to distinguish active code from orphaned definitions.
DFS Traversal in find_dead_code
The find_dead_code function implements a depth-first search (DFS) over the graph’s adjacency lists beginning at entry points. As the traversal visits each node, it marks reachable definitions. After completion, any definition node remaining unmarked is classified as dead code. According to the code-review-graph source code, this function filters out false positives—such as symbols accessed via reflection or external entry points—using language-specific heuristics before reporting.
Reporting Dead Code
The detector returns a structured list containing file paths, line numbers, and descriptions of unreachable symbols. This data powers the CLI’s detect_dead_code command and generates suggestions for pull request reviews.
Suggesting Renames with Context Awareness
The rename engine analyzes identifier patterns and validates potential changes against the graph to prevent breaking changes.
Identifier Similarity Scoring
The suggest_refactorings function in code_review_graph/refactor.py extracts all identifier names from the graph and computes similarity scores using a combination of Levenshtein distance and token-level matching that understands naming conventions like camelCase and snake_case. High-scoring pairs indicate potential renaming opportunities where one name better describes the underlying logic.
Collision Prevention via Graph Constraints
Before suggesting a rename, the engine consults the graph’s import edges and scope information stored in graph.py. Candidates that would cause name collisions or break existing imports are automatically discarded. This ensures that suggested renames are syntactically safe within the specific context of the codebase.
Preview and Application Workflow
The refactoring workflow supports human-in-the-loop confirmation through a staged preview system:
- Preview Generation:
rename_previewassembles a provisional edit set mapping each rename to its definition and all reference locations. It stores this state in the_pending_refactorscache keyed by a short randomrefactor_id. - Diff Inspection: Users retrieve a human-readable diff showing exactly which files and lines will change.
- Application:
apply_refactorvalidates the preview against the current repository state, writes the edits to disk if validation passes, and cleans up the cache entry.
Practical Implementation Examples
The following examples demonstrate how to invoke the dead-code detector and rename engine programmatically.
Detecting Dead Code
from code_review_graph.refactor import find_dead_code
# `store` is a GraphStore instance created by the CLI.
dead_items = find_dead_code(store)
for item in dead_items:
print(f"Dead: {item.file_path}:{item.lineno} – {item.kind}")
Generating Rename Suggestions
from code_review_graph.refactor import suggest_refactorings
suggestions = suggest_refactorings(store)
for s in suggestions:
print(f"Rename `{s.old_name}` → `{s.new_name}` in {s.file_path}")
Previewing and Applying Renames
from code_review_graph.refactor import rename_preview, apply_refactor
# Request a preview for a specific rename
preview = rename_preview(
store,
edits=[{
"path": "src/utils.py",
"old_name": "calc_sum",
"new_name": "compute_total"
}]
)
refactor_id = preview["refactor_id"]
print(preview["diff"]) # Human-readable diff
# Apply the changes (set dry_run=True to validate without writing)
result = apply_refactor(refactor_id, repo_root, dry_run=False)
print(result["message"])
Summary
code_review_graph/refactor.pycontains the corefind_dead_codefunction that performs DFS reachability analysis from entry points to identify unused definitions.- The call graph constructed in
graph.pyand populated byparser.pyandjedi_resolver.pyprovides the structural data needed for both dead-code detection and rename validation. - Rename suggestions rely on Levenshtein distance and token matching algorithms, filtered by import edges to prevent collisions.
- The preview workflow using
rename_previewandapply_refactorwith transientrefactor_idcaching ensures safe, reviewable refactoring operations. - All analyses are language-agnostic at the graph level, with language-specific details handled by dedicated resolvers.
Frequently Asked Questions
How does find_dead_code determine which symbols are entry points?
The function identifies entry points by scanning for exported symbols, main function definitions, test suite entry points, and explicitly declared public APIs. It then performs a depth-first search from these nodes across the adjacency lists in graph.py, marking all visited nodes as reachable. Any definition node not visited during this traversal is flagged as potentially dead code.
What algorithm does the rename suggester use to calculate similarity?
The suggest_refactorings implementation computes similarity using a combination of Levenshtein distance for string edit distance and token-level matching that splits identifiers on case changes and underscores. This hybrid approach detects similarities between different naming conventions like calculateTotal and calc_total while filtering out candidates that would conflict with existing imports or scope boundaries.
Can the refactoring tools handle dynamic language features like reflection?
The tools apply language-specific heuristics in find_dead_code to filter false positives that may arise from dynamic features such as reflection or string-based imports. While the static graph cannot fully resolve dynamic runtime behavior, these heuristics exclude symbols likely accessed via non-static paths from dead-code reports, reducing false positives.
How is the preview state persisted between rename_preview and apply_refactor?
When rename_preview generates a refactoring plan, it stores the provisional edit set in an internal _pending_refactors dictionary keyed by a randomly generated refactor_id string. This transient cache maintains the state until apply_refactor validates and applies the changes, at which point the entry is removed. The design allows users to review diffs and confirm changes without locking files indefinitely.
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 →