Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions code_review_graph/refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import time
import uuid
from pathlib import Path
from typing import Any, Optional
from typing import Any, Optional, Union

from .flows import _has_framework_decorator, _matches_entry_name
from .graph import GraphStore, _sanitize_name
Expand Down Expand Up @@ -241,6 +241,7 @@ def find_dead_code(
store: GraphStore,
kind: Optional[str] = None,
file_pattern: Optional[str] = None,
root: Optional[Union[str, Path]] = None,
) -> list[dict[str, Any]]:
"""Find functions/classes with no callers, no test refs, no importers, and no references.

Expand All @@ -260,10 +261,11 @@ def find_dead_code(
store: The GraphStore instance.
kind: Optional filter (e.g. ``"Function"`` or ``"Class"``).
file_pattern: Optional file-path substring filter.
root: Optional repo root path for computing ``relative_path``.

Returns:
List of dead-code dicts with name, qualified_name, kind, file, line,
and a top-level ``caveats`` note.
List of dead-code dicts with name, qualified_name, kind, file_path,
relative_path, line, and language fields.
"""
# Query candidate nodes.
candidates = store.get_nodes_by_kind(
Expand Down Expand Up @@ -555,12 +557,22 @@ def _is_plausible_caller(
break

if not has_callers:
if root:
try:
rel = str(Path(node.file_path).relative_to(root))
except ValueError:
rel = node.file_path
else:
rel = node.file_path
dead.append({
"name": _sanitize_name(node.name),
"qualified_name": _sanitize_name(node.qualified_name),
"kind": node.kind,
"file": node.file_path,
"file_path": node.file_path,
"relative_path": rel,
"line": node.line_start,
"language": node.language,
})

logger.info("find_dead_code: found %d dead symbols", len(dead))
Expand Down
2 changes: 1 addition & 1 deletion code_review_graph/tools/refactor_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def refactor_func(

elif mode == "dead_code":
dead = find_dead_code(
store, kind=kind, file_pattern=file_pattern
store, kind=kind, file_pattern=file_pattern, root=root
)
result = {
"status": "ok",
Expand Down
16 changes: 16 additions & 0 deletions tests/test_refactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,22 @@ def test_find_dead_code(self):
dead_names = {d["name"] for d in dead}
assert "dead_func" in dead_names

def test_find_dead_code_response_fields(self):
"""dead_code entries include file_path, relative_path, and language."""
dead = find_dead_code(self.store, root="/repo")
entry = next(d for d in dead if d["name"] == "dead_func")
assert entry["file_path"] == "/repo/app.py"
assert entry["relative_path"] == "app.py"
assert entry["language"] == "python"
# backward compat: 'file' key still present
assert entry["file"] == "/repo/app.py"

def test_find_dead_code_relative_path_without_root(self):
"""Without root, relative_path falls back to file_path."""
dead = find_dead_code(self.store)
entry = next(d for d in dead if d["name"] == "dead_func")
assert entry["relative_path"] == "/repo/app.py"

def test_find_dead_code_excludes_called(self):
"""find_dead_code does NOT include functions with callers."""
dead = find_dead_code(self.store)
Expand Down