Skip to content

Commit 9401b7a

Browse files
committed
refactor(renderer): align clone detection wording across surfaces
1 parent f469dbb commit 9401b7a

4 files changed

Lines changed: 140 additions & 14 deletions

File tree

src/slop_detector/renderer_glossary.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
"How structurally connected the project's files are to each other; "
2929
"higher means more cohesive."
3030
)
31+
_MEANS_EXACT_DUPLICATE = (
32+
"Two or more functions in this file are exact duplicates after normalizing "
33+
"local names and parameters."
34+
)
35+
_MEANS_CLONE_CLUSTER = (
36+
"Several functions in this file share almost the same AST structure and "
37+
"should be reviewed for copy-paste fragmentation."
38+
)
3139

3240

3341
def _deficit_health(value: float) -> str:
@@ -99,7 +107,7 @@ def file_metric_rows(fr: Any) -> List[Dict[str, str]]:
99107
"""Per-file metric descriptors (reuses the project-level health bands and
100108
meanings). A single file has no weighted aggregate, so only the four core
101109
dimensions are reported."""
102-
return [
110+
rows = [
103111
{
104112
"label": "Deficit Score",
105113
"value": f"{fr.deficit_score:.1f}/100",
@@ -129,6 +137,45 @@ def file_metric_rows(fr: Any) -> List[Dict[str, str]]:
129137
"means": _MEANS_DDC,
130138
},
131139
]
140+
clone_row = clone_metric_row(fr)
141+
if clone_row:
142+
rows.append(clone_row)
143+
return rows
144+
145+
146+
def clone_metric_row(fr: Any) -> Dict[str, str] | None:
147+
clone_issues = [
148+
issue
149+
for issue in getattr(fr, "pattern_issues", []) or []
150+
if getattr(issue, "pattern_id", None) in {"function_clone_cluster", "exact_duplicate_pair"}
151+
]
152+
if not clone_issues:
153+
return None
154+
155+
top = max(clone_issues, key=_clone_issue_rank)
156+
pattern_id = getattr(top, "pattern_id", "")
157+
severity = str(getattr(getattr(top, "severity", None), "value", "warning")).lower()
158+
health = "bad" if severity == "critical" else "warn"
159+
if pattern_id == "exact_duplicate_pair":
160+
value = f"{severity.upper()} - exact duplicate functions"
161+
means = _MEANS_EXACT_DUPLICATE
162+
else:
163+
value = f"{severity.upper()} - near-identical function cluster"
164+
means = _MEANS_CLONE_CLUSTER
165+
return {
166+
"label": "Clone Detection",
167+
"value": value,
168+
"direction": "Lower",
169+
"health": health,
170+
"means": means,
171+
}
172+
173+
174+
def _clone_issue_rank(issue: Any) -> tuple[int, int]:
175+
severity = str(getattr(getattr(issue, "severity", None), "value", "")).lower()
176+
severity_rank = {"critical": 2, "high": 1}.get(severity, 0)
177+
pattern_rank = 1 if getattr(issue, "pattern_id", "") == "exact_duplicate_pair" else 0
178+
return severity_rank, pattern_rank
132179

133180

134181
def coherence_display(result: Any) -> Dict[str, str]:

src/slop_detector/renderer_rich.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from slop_detector.question_generator import QuestionGenerator
99
from slop_detector.renderer_glossary import (
1010
DEFICIT_BANDS,
11+
clone_metric_row,
1112
coherence_display,
1213
file_metric_rows,
1314
next_steps,
@@ -319,23 +320,22 @@ def _build_metrics_table(result) -> "Table":
319320
"Optional ML secondary signal.",
320321
)
321322

322-
clone_pattern_ids = {"function_clone_cluster", "exact_duplicate_pair"}
323-
clone_issues = [
324-
i
325-
for i in getattr(result, "pattern_issues", [])
326-
if getattr(i, "pattern_id", None) in clone_pattern_ids
327-
]
328-
if clone_issues:
329-
top = clone_issues[0]
330-
sev = getattr(top, "severity", None)
331-
sev_val = sev.value if sev is not None else ""
332-
clone_color = "red" if sev_val == "critical" else "yellow"
323+
clone_row = clone_metric_row(result)
324+
if clone_row:
325+
clone_color = "red" if clone_row["health"] == "bad" else "yellow"
333326
t.add_row(
334327
"Clone Detection:",
335-
f"[{clone_color}]{sev_val.upper()} — structural duplicates detected[/{clone_color}]",
328+
f"[{clone_color}]{clone_row['value']}[/{clone_color}]",
329+
clone_row["direction"],
330+
clone_row["means"],
336331
)
337332
else:
338-
t.add_row("Clone Detection:", "[green]PASS[/green]")
333+
t.add_row(
334+
"Clone Detection:",
335+
"[green]PASS[/green]",
336+
"Lower",
337+
"No exact duplicate pair or near-identical function cluster was detected.",
338+
)
339339

340340
return t
341341

tests/test_cli.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def test_generate_text_report_single_file():
8080
deficit_score=25.0,
8181
status=SlopStatus.CLEAN,
8282
warnings=["Test warning"],
83+
pattern_issues=[],
8384
)
8485

8586
report = generate_text_report(result)
@@ -252,6 +253,58 @@ def test_generate_markdown_report_single_file():
252253
assert "file.py" in report
253254

254255

256+
def test_generate_text_report_single_file_shows_exact_duplicate_clone_row():
257+
result = FileAnalysis(
258+
file_path="/test/file.py",
259+
ldr=LDRResult(100, 80, 20, 0.80, "A"),
260+
inflation=InflationResult(5, 2.0, 0.5, "PASS", ["neural"], []),
261+
ddc=DDCResult(["numpy"], ["numpy"], [], [], [], 1.0, "EXCELLENT"),
262+
deficit_score=25.0,
263+
status=SlopStatus.CLEAN,
264+
warnings=[],
265+
pattern_issues=[
266+
type(
267+
"Issue",
268+
(),
269+
{
270+
"pattern_id": "exact_duplicate_pair",
271+
"severity": type("Severity", (), {"value": "high"})(),
272+
},
273+
)()
274+
],
275+
)
276+
277+
report = generate_text_report(result)
278+
assert "Clone Detection" in report
279+
assert "exact duplicate functions" in report
280+
281+
282+
def test_generate_markdown_report_single_file_shows_clone_cluster_row():
283+
result = FileAnalysis(
284+
file_path="/test/file.py",
285+
ldr=LDRResult(100, 80, 20, 0.80, "A"),
286+
inflation=InflationResult(5, 2.0, 0.5, "PASS", ["neural"], []),
287+
ddc=DDCResult(["numpy"], ["numpy"], [], [], [], 1.0, "EXCELLENT"),
288+
deficit_score=25.0,
289+
status=SlopStatus.CLEAN,
290+
warnings=[],
291+
pattern_issues=[
292+
type(
293+
"Issue",
294+
(),
295+
{
296+
"pattern_id": "function_clone_cluster",
297+
"severity": type("Severity", (), {"value": "critical"})(),
298+
},
299+
)()
300+
],
301+
)
302+
303+
report = generate_markdown_report(result)
304+
assert "Clone Detection" in report
305+
assert "near-identical function cluster" in report
306+
307+
255308
def test_generate_markdown_report_project():
256309
"""Test generate_markdown_report for project."""
257310
file_result = FileAnalysis(

tests/test_renderer_glossary.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from slop_detector.renderer_glossary import (
66
DEFICIT_BANDS,
7+
clone_metric_row,
78
file_metric_rows,
89
next_steps,
910
project_metric_rows,
@@ -101,6 +102,7 @@ def test_file_metric_rows_shape_and_shared_bands():
101102
ldr=SimpleNamespace(ldr_score=0.30),
102103
inflation=SimpleNamespace(inflation_score=2.0),
103104
ddc=SimpleNamespace(usage_ratio=0.30),
105+
pattern_issues=[],
104106
)
105107
rows = file_metric_rows(fr)
106108
assert [r["label"] for r in rows][0] == "Deficit Score"
@@ -113,6 +115,30 @@ def test_file_metric_rows_shape_and_shared_bands():
113115
assert all(r["health"] == "bad" for r in rows)
114116

115117

118+
def test_clone_metric_row_distinguishes_exact_duplicate_pair():
119+
issue = SimpleNamespace(
120+
pattern_id="exact_duplicate_pair",
121+
severity=SimpleNamespace(value="high"),
122+
)
123+
row = clone_metric_row(SimpleNamespace(pattern_issues=[issue]))
124+
assert row is not None
125+
assert row["label"] == "Clone Detection"
126+
assert "exact duplicate functions" in row["value"]
127+
assert "normalizing local names" in row["means"]
128+
129+
130+
def test_clone_metric_row_distinguishes_clone_cluster():
131+
issue = SimpleNamespace(
132+
pattern_id="function_clone_cluster",
133+
severity=SimpleNamespace(value="critical"),
134+
)
135+
row = clone_metric_row(SimpleNamespace(pattern_issues=[issue]))
136+
assert row is not None
137+
assert "near-identical function cluster" in row["value"]
138+
assert "AST structure" in row["means"]
139+
assert row["health"] == "bad"
140+
141+
116142
def test_next_steps_ddc_concern_recommends_unused_deps():
117143
steps = next_steps(_result(avg_ddc=0.30, deficit_files=1, priority_hotspots=[]))
118144
assert any("unused-deps" in s for s in steps)

0 commit comments

Comments
 (0)