Skip to content
Closed
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
38 changes: 37 additions & 1 deletion graphify/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,8 @@ def count_words(path: Path) -> int:
".tox", ".nox", ".eggs", "*.egg-info", # nox is tox's successor, same .nox/ venv shape (#1804)
"graphify-out", GRAPHIFY_OUT_NAME, # never treat own output as source input (#524); honour GRAPHIFY_OUT (#1423)
# Coverage/test-artefact dirs — generated, never architecturally meaningful
"coverage", "lcov-report", # Vitest/Istanbul/nyc HTML reports (#870)
# "coverage"/"lcov-report" are gated on real report markers below (#2339) — a
# real source package named coverage/ was silently pruned with no trace.
"visual-tests", "visual-test", # Playwright/visual-regression bundles (#869)
"__snapshots__", # Jest/Vitest snapshot dir (unambiguous)
"storybook-static", # Storybook production build output
Expand Down Expand Up @@ -848,6 +849,34 @@ def _has_venv_markers(d: "Path") -> bool:
return False


def _has_coverage_markers(d: "Path") -> bool:
"""True only when *d* has actual coverage/test-report structure on disk.

"coverage"/"lcov-report" is a real source-directory name too (e.g. a
package named coverage/), so pruning it by name alone silently drops
legitimate source with no trace (#2339). Prune it only on real evidence:
known report files (lcov.info, coverage-final.json, clover.xml,
coverage.xml, cobertura.xml), an index.html alongside Istanbul/nyc's
static assets (base.css/prettify.js/block-navigation.js/sorter.js), any
*.gcov file, or a nested lcov-report/ subdir.
"""
try:
for name in ("lcov.info", "coverage-final.json", "clover.xml", "coverage.xml", "cobertura.xml"):
if (d / name).is_file():
return True
if (d / "index.html").is_file():
for asset in ("base.css", "prettify.js", "block-navigation.js", "sorter.js"):
if (d / asset).is_file():
return True
if next(d.glob("*.gcov"), None) is not None:
return True
if (d / "lcov-report").is_dir():
return True
except OSError:
pass
return False


def _is_noise_dir(part: str, parent: "Path | None" = None) -> bool:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_is_noise_dir()

8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

"""Return True if this directory name looks like a venv, cache, or dep dir."""
if part in _SKIP_DIRS:
Expand All @@ -858,6 +887,13 @@ def _is_noise_dir(part: str, parent: "Path | None" = None) -> bool:
if parent is None:
return False # cannot verify; keep a possibly-real code dir
return _has_venv_markers(parent / part)
if part in ("coverage", "lcov-report"):
# Ambiguous: a real coverage report OR a real source dir (e.g. a
# package named coverage/). Prune only on actual report evidence,
# mirroring the "snapshots" gating (#1666/#2058/#2339).
if parent is None:
return False # cannot verify; keep a possibly-real code dir
return _has_coverage_markers(parent / part)
if part == "snapshots":
# Prune only when it looks like an actual JS/Vitest snapshot dir.
if parent is None:
Expand Down
74 changes: 73 additions & 1 deletion tests/test_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import unicodedata
import pytest
from pathlib import Path
from graphify.detect import classify_file, count_words, detect, detect_incremental, save_manifest, FileType, _looks_like_paper, _is_ignored, _load_graphifyignore, _is_sensitive
from graphify.detect import classify_file, count_words, detect, detect_incremental, save_manifest, FileType, _looks_like_paper, _is_ignored, _load_graphifyignore, _is_sensitive, _is_noise_dir
from graphify import detect as detect_mod

FIXTURES = Path(__file__).parent / "fixtures"
Expand Down Expand Up @@ -699,6 +699,78 @@ def test_detect_skips_visual_tests_dir(tmp_path):
assert any("app.py" in f for f in all_files)


def test_detect_keeps_coverage_code_package(tmp_path):
"""#2339: a real Python package named coverage/ is not a report dir and
must not be pruned by name alone."""
pkg = tmp_path / "coverage"
pkg.mkdir()
(pkg / "__init__.py").write_text("")
(pkg / "impact.py").write_text("def measure(): pass")
(tmp_path / "main.py").write_text("import coverage")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
assert any("impact.py" in f for f in all_files)
assert any("main.py" in f for f in all_files)


def test_detect_skips_real_lcov_report(tmp_path):
"""#2339: a genuine Istanbul/nyc report dir is still pruned on real evidence."""
cov = tmp_path / "coverage" / "lcov-report"
cov.mkdir(parents=True)
(cov / "index.html").write_text("<html>coverage report</html>")
(cov / "base.css").write_text("body {}")
(tmp_path / "main.py").write_text("def hello(): pass")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
cov_prefix = str(tmp_path / "coverage")
assert not any(f.startswith(cov_prefix) for f in all_files)
assert any("main.py" in f for f in all_files)


def test_detect_skips_real_lcov_info(tmp_path):
"""#2339: a bare coverage/ dir holding lcov.info is still pruned."""
cov = tmp_path / "coverage"
cov.mkdir()
(cov / "lcov.info").write_text("SF:src/foo.js\nend_of_record")
(tmp_path / "main.py").write_text("def hello(): pass")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
cov_prefix = str(tmp_path / "coverage")
assert not any(f.startswith(cov_prefix) for f in all_files)
assert any("main.py" in f for f in all_files)


def test_detect_keeps_nested_coverage_code_package(tmp_path):
"""#2339: name match at any depth — a nested real coverage/ package is kept."""
pkg = tmp_path / "src" / "pkg" / "coverage"
pkg.mkdir(parents=True)
(pkg / "__init__.py").write_text("")
(pkg / "impact.py").write_text("def measure(): pass")
(tmp_path / "main.py").write_text("import src.pkg.coverage")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
assert any("impact.py" in f for f in all_files)


def test_detect_skips_nested_real_lcov_report(tmp_path):
"""#2339: name match at any depth — a nested real report dir is still pruned."""
cov = tmp_path / "src" / "pkg" / "coverage" / "lcov-report"
cov.mkdir(parents=True)
(cov / "index.html").write_text("<html>coverage report</html>")
(cov / "base.css").write_text("body {}")
(tmp_path / "main.py").write_text("def hello(): pass")
result = detect(tmp_path)
all_files = [f for files in result["files"].values() for f in files]
cov_prefix = str(tmp_path / "src" / "pkg" / "coverage")
assert not any(f.startswith(cov_prefix) for f in all_files)
assert any("main.py" in f for f in all_files)


def test_is_noise_dir_coverage_no_parent_returns_false():
"""#2339: with no parent to verify, coverage/ is kept, not pruned by name."""
assert _is_noise_dir("coverage", None) is False


def test_detect_skips_snapshots_dir(tmp_path):
"""__snapshots__/ and real jest/vitest snapshots/ dirs are artefacts — excluded."""
(tmp_path / "__snapshots__").mkdir()
Expand Down
Loading