Skip to content

Commit 97608ef

Browse files
ayushcodes10safishamsi
authored andcommitted
Add regression tests for the symbolless file warning
Covers a plain data literal getting named and marked as likely data, a real function staying silent, no overlap with the existing syntax error warning on a genuinely broken file, and the total size count across more than one file. Confirmed to fail against pre fix code. (cherry picked from commit 32ad059)
1 parent f81c4e5 commit 97608ef

2 files changed

Lines changed: 90 additions & 13 deletions

File tree

‎graphify/extract.py‎

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7655,22 +7655,36 @@ def _describe_syntax_error(rel: str, line: "int | None", kept: int) -> str:
76557655

76567656
# #3946: a code file that parses CLEANLY (no parse_errors — distinct from
76577657
# the #2551/#2599 case just above, where _syntax_error_files already
7658-
# explains the loss) but still yields only its own bare file node is
7659-
# usually a plain data literal the AST extractor has nothing to model
7660-
# (`const BANK = [...]`), not a code file with no symbols. Nothing
7661-
# reported this before; the file silently entered the graph as a bare,
7658+
# explains the loss) but yields NOTHING beyond its own bare file node often
7659+
# is a plain data literal the AST extractor has nothing to model
7660+
# (`const BANK = [...]`). It silently entered the graph as a bare,
76627661
# zero-edge node, indistinguishable from a legitimately symbol-free file.
7662+
#
7663+
# Scope this to EXACTLY one node (the unconditional file node, engine.py):
7664+
# - `== 1`, not `<= 1`, so the 0-node domain stays owned by #1666 (zero
7665+
# nodes / retry) and is not double-reported with contradictory advice;
7666+
# - skip `skipped` (#1224/#2879 intentional data declines, like #1666) and
7667+
# no-extractor files (#1689 already reports those as unsupported code);
7668+
# - skip empty/whitespace-only files (an empty `__init__.py`, `py.typed`)
7669+
# — there is nothing to model, so the warning would be pure noise.
7670+
# The message stays neutral ("no symbols"): a def-less script or thin
7671+
# module is symbol-less *code*, not necessarily data.
76637672
_symbolless_files: list[tuple[str, int]] = []
76647673
for i, _p in enumerate(paths):
76657674
_res = per_file[i] or {}
7666-
if _res.get("parse_errors") or _res.get("error"):
7675+
if _res.get("parse_errors") or _res.get("error") or _res.get("skipped"):
76677676
continue
7668-
if len(_res.get("nodes", [])) <= 1:
7669-
try:
7670-
_size = _p.stat().st_size
7671-
except OSError:
7672-
_size = 0
7673-
_symbolless_files.append((os.path.relpath(str(_p), str(root)).replace("\\", "/"), _size))
7677+
if _get_extractor(_p) is None:
7678+
continue
7679+
if len(_res.get("nodes", [])) != 1:
7680+
continue
7681+
try:
7682+
_size = _p.stat().st_size
7683+
if not _p.read_text(encoding="utf-8", errors="ignore").strip():
7684+
continue # empty / whitespace-only: nothing to model, no signal
7685+
except OSError:
7686+
_size = 0
7687+
_symbolless_files.append((os.path.relpath(str(_p), str(root)).replace("\\", "/"), _size))
76747688
if _symbolless_files:
76757689
_total_bytes = sum(size for _, size in _symbolless_files)
76767690
_total_mb = _total_bytes / (1024 * 1024)
@@ -7681,8 +7695,8 @@ def _describe_syntax_error(rel: str, line: "int | None", kept: int) -> str:
76817695
)
76827696
print(
76837697
f" warning: {len(_symbolless_files)} code file(s) yielded no symbols "
7684-
f"({_total_mb:.2f} MB); they may be data rather than code: "
7685-
f"{_shown_sl}{_more_sl}",
7698+
f"({_total_mb:.2f} MB) — a data file, or source this extractor models "
7699+
f"no symbols for: {_shown_sl}{_more_sl}",
76867700
file=sys.stderr, flush=True,
76877701
)
76887702

‎tests/test_partial_extraction_warning.py‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,66 @@ def test_the_citation_is_gone_from_the_source_not_just_one_path():
8080
assert "may be partially extracted: {_shown}{_more} (#2551)" not in text
8181
assert "no symbols extracted" in text
8282
assert "symbol(s) extracted" in text
83+
84+
85+
# ── #3946: a clean-parse file that yields nothing but its own file node ─────
86+
87+
def _data_only_fixture(tmp_path):
88+
"""A plain data literal with no functions, classes or imports — the AST
89+
extractor has nothing to model, but the file parses with zero errors, so
90+
the syntax-error warning above must stay silent for it."""
91+
f = tmp_path / "data.js"
92+
f.write_text("module.exports = [{a: 1}, {b: 2}];\n", encoding="utf-8")
93+
return f
94+
95+
96+
def test_symbolless_warning_names_the_file(tmp_path, capsys):
97+
err = _run(tmp_path, [_data_only_fixture(tmp_path)], capsys)
98+
assert "yielded no symbols" in err, err
99+
assert "data.js" in err
100+
# neutral wording: a symbol-less file may be data OR source the extractor
101+
# models nothing for — do NOT assert it is "data rather than code".
102+
assert "may be data rather than code" not in err
103+
104+
105+
def test_symbolless_warning_is_silent_for_an_empty_init(tmp_path, capsys):
106+
"""An empty __init__.py is symbol-less *code*, ubiquitous in Python
107+
packages. It has nothing to model, so the warning must stay silent rather
108+
than fire on essentially every package scan."""
109+
pkg = tmp_path / "pkg"
110+
pkg.mkdir()
111+
(pkg / "__init__.py").write_text("", encoding="utf-8")
112+
err = _run(tmp_path, [pkg / "__init__.py"], capsys)
113+
assert "yielded no symbols" not in err
114+
115+
116+
def test_symbolless_warning_is_silent_for_a_whitespace_only_file(tmp_path, capsys):
117+
f = tmp_path / "blank.py"
118+
f.write_text("\n \n\t\n", encoding="utf-8")
119+
err = _run(tmp_path, [f], capsys)
120+
# whitespace-only: nothing to model, so the strip-empty guard keeps it quiet
121+
assert "yielded no symbols" not in err
122+
123+
124+
def test_symbolless_warning_does_not_fire_for_a_real_function(tmp_path, capsys):
125+
f = tmp_path / "real.js"
126+
f.write_text("function run() { return 1; }\n", encoding="utf-8")
127+
err = _run(tmp_path, [f], capsys)
128+
assert "yielded no symbols" not in err
129+
130+
131+
def test_symbolless_warning_does_not_overlap_the_syntax_error_warning(tmp_path, capsys):
132+
"""A file already explained by the partial-extraction warning (a genuine
133+
parse error) must not ALSO be counted as a clean-parse data file."""
134+
err = _run(tmp_path, [_partial_parse_fixture(tmp_path)], capsys)
135+
assert "yielded no symbols" not in err
136+
137+
138+
def test_symbolless_warning_counts_total_size(tmp_path, capsys):
139+
a = tmp_path / "a.js"
140+
a.write_text("module.exports = [1, 2, 3];\n", encoding="utf-8")
141+
b = tmp_path / "b.js"
142+
b.write_text("export default [4, 5, 6];\n", encoding="utf-8")
143+
err = _run(tmp_path, [a, b], capsys)
144+
assert "2 code file(s) yielded no symbols" in err
145+
assert "a.js" in err and "b.js" in err

0 commit comments

Comments
 (0)