perf(hunt): the source producer walks and reads the product tree once per change, not once per row

facts_for walked the whole product tree and re-read every product file for each candidate row; on a Laravel
application one change of 33 rows took four minutes, 96 percent of it there. The producer context already
holds the product file list and the texts it read for entry points, so facts_for and callers_of now accept
them. Same facts, tested by a call whose tree walk raises and whose file on disk is emptied after reading.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-14 09:35:16 +02:00
co-authored by Claude Fable 5.1
parent 4063fd1562
commit 86d70831c8
3 changed files with 42 additions and 9 deletions
+13
View File
@@ -135,3 +135,16 @@ def test_parse_rejection_is_not_an_empty_result(monkeypatch):
)
with pytest.raises(ValueError, match="source parse failed"):
source.ranges("app.py", "def broken(", "python")
def test_facts_for_reuses_supplied_product_files_and_texts(tmp_path, monkeypatch):
"""With files and texts supplied, facts_for neither walks the tree nor reads the files it was given."""
from openultrasast import source
(tmp_path / "app.py").write_text("def handler(value):\n return value\n")
(tmp_path / "other.py").write_text("from app import handler\n\n\ndef run():\n return handler(1)\n")
monkeypatch.setattr(source, "product_files", lambda root: (_ for _ in ()).throw(AssertionError("walked the tree")))
texts = {"other.py": (tmp_path / "other.py").read_text()}
(tmp_path / "other.py").write_text("") # the supplied text, not the disk, is what callers_of reads
facts = source.facts_for(tmp_path, "app.py", 2, entry_points=[], files=["app.py", "other.py"], texts=texts)
assert facts.function == "handler" and facts.callers == (("other.py", 5),)