diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 426be29ce..e3ee82729 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,41 @@ jobs: - name: Run unit tests run: uv run pytest tests/unit/ -v --tb=short + # --------------------------------------------------------------------------- + # tree-sitter floor — installs the declared minimum tree-sitter version and + # compiles every language's query against it. `uv sync` alone always + # resolves the newest tree-sitter, which is why #2165 (13 of 27 language + # queries failing to compile against tree-sitter 0.23.x/0.24.x, with each + # failure silently indexing the language at zero symbols) went uncaught. + # Deliberately narrow: only tree-sitter itself is pinned down to the floor, + # not `--resolution lowest-direct` for the whole dependency set, which + # would fail on unrelated loose floors elsewhere in this repo. + # --------------------------------------------------------------------------- + test-tree-sitter-floor: + name: tree-sitter floor (query compilation) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + version: "latest" + enable-cache: true + + - name: Install dependencies + run: uv sync --all-packages + + - name: Pin tree-sitter to the declared floor + run: uv pip install "tree-sitter==0.25.0" + + - name: Run query-compilation test + # --no-sync: plain `uv run` re-syncs the venv from uv.lock before + # running, which would silently reinstall the newest tree-sitter and + # undo the pin above. + run: uv run --no-sync pytest tests/unit/ingestion/parser/test_query_compilation.py -v --tb=short + # --------------------------------------------------------------------------- # Python lint — ruff. Deliberately its own job rather than a step inside the # test matrix: the result is identical on 3.11/3.12/3.13 (target-version is diff --git a/pyproject.toml b/pyproject.toml index f75baaad0..4a23fc918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ # HTTP client "httpx>=0.27,<1", # AST parsing - "tree-sitter>=0.23,<1", + "tree-sitter>=0.25,<1", "tree-sitter-python>=0.23,<1", "tree-sitter-typescript>=0.23,<1", "tree-sitter-javascript>=0.23,<1", diff --git a/tests/unit/ingestion/parser/test_query_compilation.py b/tests/unit/ingestion/parser/test_query_compilation.py new file mode 100644 index 000000000..be9067899 --- /dev/null +++ b/tests/unit/ingestion/parser/test_query_compilation.py @@ -0,0 +1,32 @@ +"""Every language's tree-sitter query must compile against its grammar. + +Regression test for #2165: with `tree-sitter` 0.23.x/0.24.x installed, 13 of +the 27 language queries failed to compile (`Impossible pattern`), and +`_load_compiled_query` swallows that failure and returns `None` — so every +file in the affected languages silently indexed with zero symbols. This +walks the full language registry, compiles each language's query directly, +and asserts on the return value rather than expecting an exception, since +that's exactly what production does. +""" + +from __future__ import annotations + +import pytest + +from repowise.core.ingestion.languages.registry import REGISTRY +from repowise.core.ingestion.parser import QUERIES_DIR, _load_compiled_query + + +def _languages_with_queries() -> list[str]: + tags = [] + for spec in REGISTRY.all_specs(): + scm_name = spec.scm_file or f"{spec.tag}.scm" + if (QUERIES_DIR / scm_name).exists(): + tags.append(spec.tag) + return sorted(tags) + + +@pytest.mark.parametrize("lang", _languages_with_queries()) +def test_query_compiles(lang: str) -> None: + query = _load_compiled_query(lang) + assert query is not None, f"{lang}: query failed to compile against its grammar" diff --git a/uv.lock b/uv.lock index 50c57980d..96babc726 100644 --- a/uv.lock +++ b/uv.lock @@ -3179,7 +3179,7 @@ requires-dist = [ { name = "structlog", specifier = ">=24,<25" }, { name = "tenacity", specifier = ">=9,<10" }, { name = "time-machine", marker = "extra == 'dev'", specifier = ">=2.14,<3" }, - { name = "tree-sitter", specifier = ">=0.23,<1" }, + { name = "tree-sitter", specifier = ">=0.25,<1" }, { name = "tree-sitter-bash", specifier = ">=0.23,<1" }, { name = "tree-sitter-c-sharp", specifier = ">=0.23,<1" }, { name = "tree-sitter-cpp", specifier = ">=0.23,<1" },