diff --git a/src/openultrasast/source.py b/src/openultrasast/source.py index 46d0c9a..bba06f7 100644 --- a/src/openultrasast/source.py +++ b/src/openultrasast/source.py @@ -483,6 +483,8 @@ def facts_for( ) -> SourceFacts: """Compute repository facts at a line; supplied entry points, product files and texts avoid repeated work. + The facts carry only the entry points declared in the file itself or in a file that calls the function. + A caller computing facts for many lines of one checkout (the hunter's feature table) walks the product tree and reads it once; without ``files`` and ``texts`` every call walked and read the whole tree again.""" root = Path(root) @@ -495,8 +497,13 @@ def facts_for( from .mapping import analyze_entry_points entry_points = analyze_entry_points(root, [build_file_target(root, root / file) for file in files]) + callers = callers_of(root, function, files=files, texts=texts, index=index) + # The entry points that reach the file: those declared in it or in a file that calls this function. The whole + # repository's list was attached before; on an application with 8000 entry points every row carried 1.5 MB + # and the hunter's prompt for any change with more than two rows was refused by the model. + reaching = {path, *(caller_path for caller_path, _ in callers)} return SourceFacts( - language, function, bounds(spans, function), callers_of(root, function, files=files, texts=texts, index=index), tuple(entry_points) + language, function, bounds(spans, function), callers, tuple(entry for entry in entry_points if entry.path in reaching) ) diff --git a/tests/test_source.py b/tests/test_source.py index f3772f7..8447a89 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -189,3 +189,19 @@ def test_generated_reason_names_build_output_only(tmp_path): assert source.generated_reason(tmp_path, "big.js") == "too_large" assert source.generated_reason(tmp_path, "app.js") is None assert source.generated_reason(tmp_path, "missing.js") is None + + +def test_facts_keep_only_the_entry_points_in_the_file_or_a_caller_file(tmp_path): + from openultrasast.mapping import EntryPointRecord + + (tmp_path / "app.py").write_text("def run(x):\n return x\n") + (tmp_path / "web.py").write_text("from app import run\n\ndef view():\n return run(1)\n") + (tmp_path / "far.py").write_text("def other():\n return 2\n") + + def entry(path): + return EntryPointRecord(path, 1, 2, None, path, "route", "public", "external", [], [], "test", "fixture") + + entries = [entry("app.py"), entry("web.py"), entry("far.py")] + facts = source.facts_for(tmp_path, "app.py", 2, entry_points=entries, files=["app.py", "web.py", "far.py"]) + assert facts.callers == (("web.py", 4),) + assert [e.path for e in facts.entry_points] == ["app.py", "web.py"]