# How the Refactor Tool Detects Dead Code Across Communities: A Complete Technical Guide

> Learn how the refactor tool detects dead code by analyzing repository graph stores and applying multi-phase filtering to identify unreachable symbols.

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

---

**The refactor tool detects dead code by analyzing the graph store representation of a repository, applying multi-phase filtering to exclude intentionally reachable symbols, and then verifying the absence of any CALLS, TESTED_BY, IMPORTS_FROM, REFERENCES, or INHERITS edges.**

The `code-review-graph` repository provides a sophisticated dead-code detection system that goes beyond simple reachability analysis. This article breaks down exactly how the tool identifies unused symbols and generates community-aware refactoring suggestions, with direct references to the source implementation in [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py).

## How Dead Code Detection Works in Three Phases

The core detection logic resides in the `find_dead_code` function. The algorithm proceeds through three distinct phases before determining whether a symbol is truly dead.

### Phase 1: Candidate Gathering

The tool begins by collecting all potential targets from the graph store. Any node of kind **Function** or **Class** becomes a candidate for dead-code analysis.

```python

# From code_review_graph/refactor.py, lines 75-78

def find_dead_code(store, kind=None):
    if kind:
        candidates = store.get_nodes_by_kind(kind)
    else:
        candidates = store.get_nodes_by_kind("Function") + \
                     store.get_nodes_by_kind("Class")

```

This query returns every symbol defined in the codebase, regardless of whether it's actually used.

### Phase 2: Filtering Out Known-Alive Symbols

The tool applies **ten distinct filters** to remove symbols that are intentionally reachable, even without explicit caller edges. These safety filters prevent false positives on framework code, test infrastructure, and language constructs.

| Filter | Purpose | Implementation |
|--------|---------|----------------|
| **Test files** | Exclude `*_test.py`, `test_*.py`, [`conftest.py`](https://github.com/tirth8205/code-review-graph/blob/main/conftest.py) | `_is_test_file` (lines 56-84) |
| **Dunder methods** | Preserve `__init__`, `__repr__`, `__str__`, etc. | Pattern matching (lines 90-104) |
| **Constructors** | Detect `new ClassName()` invocations | Constructor edge analysis (lines 115-124) |
| **Mock/stub variables** | Skip variables matching `_MOCK_NAME_RE` | Regex pattern `_MOCK_NAME_RE` (lines 129-138) |
| **Framework base classes** | Exclude subclasses of Django, Flask, FastAPI bases | `_FRAMEWORK_BASE_CLASSES` (lines 144-155) |
| **CDK/IaC classes** | Preserve classes with CDK suffixes | `_CDK_CLASS_SUFFIXES` (lines 166-176) |
| **Type annotations** | Keep types referenced in signatures | `_collect_type_referenced_names` (lines 184-199) |
| **Entry points** | Detect `main()`, `cli()`, `@click.command()`, etc. | `_is_entry_point` (lines 206-226) |
| **Framework decorators** | Preserve `@property`, `@abstractmethod`, `@staticmethod` | `_has_framework_decorator` (lines 232-254) |
| **Abstract overrides & dataclasses** | Keep protocol implementations | Special-case handling (lines 258-306) |

Each filter is implemented as an early-exit check. If any filter matches, the symbol is immediately marked as **alive** and removed from further consideration.

### Phase 3: Final Deadness Verification

After filtering, remaining candidates undergo strict reachability verification. The tool checks for five edge types that indicate usage:

- **CALLS**: Another function invokes this symbol
- **TESTED_BY**: A test case exercises this symbol
- **IMPORTS_FROM**: Another module imports from this symbol
- **REFERENCES**: Static references exist without direct calls
- **INHERITS**: Class inheritance relationships

```python

# Simplified logic from lines 374-418, 420-463, 465-511

def _is_actually_dead(node, store):
    # Check all usage edge types

    if store.get_edges_from(node.qn, "CALLS"): return False
    if store.get_edges_from(node.qn, "TESTED_BY"): return False
    if store.get_edges_from(node.qn, "IMPORTS_FROM"): return False
    if store.get_edges_from(node.qn, "REFERENCES"): return False
    if store.get_edges_from(node.qn, "INHERITS"): return False
    
    # For classes: check if any member has callers

    if node.kind == "Class":
        for member in node.members:
            if store.get_edges_from(member.qn, "CALLS"):
                return False
    
    # For methods: polymorphic dispatch check

    if node.kind == "Function" and node.parent_class:
        base_callers = _find_base_class_callers(node, store)
        if base_callers:
            return False
    
    return True

```

The **polymorphic dispatch check** is particularly important: if a method overrides a base class implementation, the tool verifies whether the *base* method has callers, since those calls may dynamically dispatch to the override.

## Community-Aware Refactoring: Moving Beyond Dead Code Detection

The dead-code detection described above is **community-agnostic**—it operates purely on graph connectivity. The community-aware features emerge in `suggest_refactorings`, which extends the analysis to detect **misplaced code**.

### How Cross-Community Detection Works

The `suggest_refactorings` function (lines 631-692, 702-726, 732-754) implements a two-pass approach:

1. **Collect dead symbols** using `find_dead_code` described above
2. **Analyze live symbols** for community misalignment

```python

# From code_review_graph/refactor.py, lines 631-692

def suggest_refactorings(store):
    suggestions = []
    
    # Pass 1: Dead code → remove suggestions

    dead = find_dead_code(store)
    for symbol in dead:
        suggestions.append({
            "type": "remove",
            "symbol": symbol["qualified_name"],
            "description": f"Remove unused {symbol['kind']} {symbol['qualified_name']}"
        })
    
    # Pass 2: Community misalignment → move suggestions

    communities = store.get_communities_list()          # lines 702-726

    node_to_community = _build_community_map(store)     # lines 732-754

    
    for func in store.get_nodes_by_kind("Function"):
        f_community = node_to_community.get(func.qn)
        caller_communities = _get_caller_communities(func, store)
        
        # All callers belong to ONE different community

        if len(caller_communities) == 1 and \
           f_community not in caller_communities:
            target_community = list(caller_communities)[0]
            suggestions.append({
                "type": "move",
                "symbol": func.qn,
                "from_community": f_community,
                "to_community": target_community,
                "description": f"Move {func.qn} from {f_community} to {target_community}"
            })
    
    return suggestions

```

A **move suggestion** is generated when:
- The function has **at least one caller** (it's not dead)
- **All callers** reside in a **single community**
- That community differs from the function's **current community**

This pattern indicates a **cohesion violation**: the implementation lives in one logical grouping but all its consumers live elsewhere.

## Practical Usage Examples

### Finding Dead Code in a Python Project

```python
from code_review_graph.graph import GraphStore
from code_review_graph.refactor import find_dead_code

store = GraphStore()
store.load_from_path("./my-project")

dead = find_dead_code(store)
for symbol in dead:
    print(f"{symbol['kind']} {symbol['qualified_name']} is dead")
    # Example output:

    # Function my_project.utils.legacy_parser is dead

    # Class my_project.models.DeprecatedSchema is dead

```

### Generating Community-Aware Refactoring Suggestions

```python
from code_review_graph.refactor import suggest_refactorings

suggestions = suggest_refactorings(store)
for s in suggestions:
    print(f"{s['type'].capitalize()}: {s['description']}")
    # Example output:

    # Remove: Remove unused Function my_project.old_api.v1_handler

    # Move: Move my_project.auth.helpers.token_decoder from auth to api_gateway

```

The **move** suggestion in this example indicates that `token_decoder` is defined in the `auth` community but only called from `api_gateway`—a clear signal for reorganization.

### Preview and Apply Refactorings Safely

```python
from code_review_graph.refactor import rename_preview, apply_refactor
import pathlib

# Preview any refactoring type (not just renames)

preview = rename_preview(store, "oldFunc", "newFunc")
if not preview:
    print("No changes needed or symbol not found")
    exit()

# Always dry-run first

dry_result = apply_refactor(
    preview["refactor_id"], 
    pathlib.Path("."), 
    dry_run=True
)

for filepath, diff in dry_result["diffs"].items():
    print(f"\n=== {filepath} ===")
    print(diff)

# Apply only after review

real_result = apply_refactor(preview["refactor_id"], pathlib.Path("."))
print(f"Applied {real_result['edits_applied']} edits across {real_result['files_modified']} files")

```

## Key Implementation Files

| File | Role in Dead Code Detection |
|------|----------------------------|
| [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py) | Core implementation: `find_dead_code`, `suggest_refactorings`, preview/apply workflow |
| [`code_review_graph/flows.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/flows.py) | Entry-point detection helpers: `_has_framework_decorator`, `_matches_entry_name` |
| [`code_review_graph/communities.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/communities.py) | Community definitions and member lookups for move suggestions |
| [`tests/test_refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_refactor.py) | Unit tests for dead-code detection and preview-apply cycle |
| [`tests/test_python_reachability.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_python_reachability.py) | Python-specific dead-code test cases |

## Summary

- **Dead code detection** operates in three phases: candidate gathering, alive-symbol filtering via ten safety checks, and final edge-based verification
- **Community-aware analysis** extends dead-code detection to identify symbols that exist in the wrong logical grouping, generating **move** suggestions when all callers belong to a different community
- **Framework-aware filters** prevent false positives on test code, decorators, entry points, and language constructs like `__init__` and abstract methods
- **Polymorphic dispatch handling** ensures method overrides are not incorrectly flagged when base-class callers exist

## Frequently Asked Questions

### How does the tool avoid false positives on dead code detection?

The tool implements **ten distinct safety filters** in [`code_review_graph/refactor.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/refactor.py) lines 56-306. These filters recognize test files, dunder methods, constructors, framework decorators, entry points, and type annotations as intentionally reachable patterns. Only symbols passing all filters undergo the final edge-based deadness check.

### What edge types indicate a symbol is alive?

The verification phase checks for five edge types: **CALLS** (direct invocation), **TESTED_BY** (test coverage), **IMPORTS_FROM** (module imports), **REFERENCES** (static references), and **INHERITS** (class inheritance). For classes, member function callers are also verified. For methods, base-class callers are checked to handle polymorphic dispatch.

### When does the tool suggest moving code versus removing it?

**Remove** suggestions apply to symbols with zero incoming edges after all filters. **Move** suggestions apply to symbols with callers that all reside in a **single different community**—defined by the `communities` table in the graph store. This distinction helps teams reorganize monorepos while preserving functionality.

### Can the dead code detection handle dynamic Python features?

Yes, though with limitations. The **polymorphic dispatch check** (lines 465-511) handles method overrides by tracing to base-class implementations. However, highly dynamic patterns like `getattr` dispatch, `eval` usage, or runtime module injection may escape detection. The tool prioritizes **soundness over completeness**—it may miss some dead code but rarely flags live code as dead.