From f81c4e5ec6a9a5de70df9c5397cd8d6105a67d24 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Fri, 2 Oct 2026 23:31:05 +0530 Subject: [PATCH] Warn when a cleanly parsed code file yields no symbols A code file that parses with zero errors but still produces nothing beyond its own bare file node is usually a plain data literal the AST extractor has nothing to model (a const array, a module export of plain data), not a code file that legitimately has no symbols. Those two cases looked identical in the output: the file silently entered the graph as an isolated, zero edge node, and nothing reported it. A real corpus measured in the issue had 88 such nodes covering roughly 40 percent of the corpus, with not one edge among them. Add a complementary check next to the existing syntax error warning, gated on the opposite condition (no parse_errors and no extractor error) so the two can never double report the same file: a file already explained by a genuine parse error or a missing optional dependency is excluded here. Fixes #3946. (cherry picked from commit 3b3ee82b1a1d1193d2e067adf300009444acb412) --- graphify/extract.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index 6ac2ffd1b..d9b4effb5 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -7653,6 +7653,39 @@ def extract( file=sys.stderr, flush=True, ) + # #3946: a code file that parses CLEANLY (no parse_errors — distinct from + # the #2551/#2599 case just above, where _syntax_error_files already + # explains the loss) but still yields only its own bare file node is + # usually a plain data literal the AST extractor has nothing to model + # (`const BANK = [...]`), not a code file with no symbols. Nothing + # reported this before; the file silently entered the graph as a bare, + # zero-edge node, indistinguishable from a legitimately symbol-free file. + _symbolless_files: list[tuple[str, int]] = [] + for i, _p in enumerate(paths): + _res = per_file[i] or {} + if _res.get("parse_errors") or _res.get("error"): + continue + if len(_res.get("nodes", [])) <= 1: + try: + _size = _p.stat().st_size + except OSError: + _size = 0 + _symbolless_files.append((os.path.relpath(str(_p), str(root)).replace("\\", "/"), _size)) + if _symbolless_files: + _total_bytes = sum(size for _, size in _symbolless_files) + _total_mb = _total_bytes / (1024 * 1024) + _shown_sl = ", ".join(rel for rel, _ in _symbolless_files[:5]) + _more_sl = ( + f" (+{len(_symbolless_files) - 5} more)" + if len(_symbolless_files) > 5 else "" + ) + print( + f" warning: {len(_symbolless_files)} code file(s) yielded no symbols " + f"({_total_mb:.2f} MB); they may be data rather than code: " + f"{_shown_sl}{_more_sl}", + file=sys.stderr, flush=True, + ) + for path, result in zip(paths, per_file): if path.suffix in (".tf", ".tfvars", ".hcl"): prepare_terraform(result, path, root)