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_preview assembles a provisional edit set mapping each rename to its definition and all reference locations. It stores this state in the _pending_refactors cache keyed by a short random refactor_id.
  • Diff Inspection: Users retrieve a human-readable diff showing exactly which files and lines will change.
  • Application: apply_refactor validates 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.py contains the core find_dead_code function that performs DFS reachability analysis from entry points to identify unused definitions.
  • The call graph constructed in graph.py and populated by parser.py and jedi_resolver.py provides 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_preview and apply_refactor with transient refactor_id caching 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →