# How the code-review-graph Refactoring Tool Detects Dead Code and Suggests Renames

> Discover how the code-review-graph refactoring tool detects dead code by analyzing graph relationships and suggests renames by identifying definition sites and references. Learn more!

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: how-to-guide
- Published: 2026-08-06

---

**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`](https://github.com/tirth8205/code-review-graph/blob/main/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`](https://github.com/tirth8205/code-review-graph/blob/main/graph.py) and leverage framework-detection helpers from [`flows.py`](https://github.com/tirth8205/code-review-graph/blob/main/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.

```python

# 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.

```python
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_point` helper (lines 89-102) excludes functions matching patterns like `main`, `handle_*`, or conventional CLI entry points.
- **Test artifact exclusion** – The `_is_test_file` check (lines 112-115) removes nodes residing in test directories or files.
- **Framework integration** – Symbols managed by frameworks are preserved using:
  - `_FRAMEWORK_BASE_CLASSES` inheritance checks
  - `_CDK_CLASS_SUFFIXES` pattern matching for CDK-style constructs
  - `_has_framework_decorator` analysis implemented in [`flows.py`](https://github.com/tirth8205/code-review-graph/blob/main/flows.py) (referenced around lines 998-1020)
- **Language specials** – Dunder methods (`__init__`, etc.), constructors, [`.d.ts`](https://github.com/tirth8205/code-review-graph/blob/main/.d.ts) type declarations, and mock variables matching `_MOCK_NAME_RE` are 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.

```python
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.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py) and provides `rename_preview`, `find_dead_code`, and `apply_refactor` utilities.
- **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 `GraphStore` interface from [`graph.py`](https://github.com/tirth8205/code-review-graph/blob/main/graph.py) and framework detection helpers from [`flows.py`](https://github.com/tirth8205/code-review-graph/blob/main/flows.py) to 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.