From 189847eb473e92747da083e955a0508713b5fb96 Mon Sep 17 00:00:00 2001 From: azizur100389 Date: Sun, 3 May 2026 00:27:10 +0100 Subject: [PATCH] fix: surface tree-sitter version-mismatch hint instead of bare TypeError When a user has an older tree-sitter installed against a newer language binding (or vice versa), Language() raises TypeError with messages like "missing 1 required positional argument: 'name'". The previous catch-all Exception handler stored that bare message in the per-file error field, giving users no actionable signal. Add a dedicated TypeError branch in _extract_generic() that returns a clearer error with the upgrade command: tree-sitter version mismatch for tree_sitter_python: ... . Try: pip install --upgrade tree-sitter tree-sitter-languages Behavior is unchanged for all non-version-mismatch errors - the broader Exception handler still runs as before. --- graphify/extract.py | 9 +++++++++ tests/test_extract.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/graphify/extract.py b/graphify/extract.py index 3c47ff1..8f0596f 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -863,6 +863,15 @@ def _extract_generic(path: Path, config: LanguageConfig) -> dict: language = Language(lang_fn()) except ImportError: return {"nodes": [], "edges": [], "error": f"{config.ts_module} not installed"} + except TypeError as e: + # tree-sitter version mismatch: old Language() expects (lib_path), + # new Language() expects (language_capsule, name). Surface a hint + # so users see the upgrade path instead of a bare TypeError. + hint = ( + f"tree-sitter version mismatch for {config.ts_module}: {e}. " + "Try: pip install --upgrade tree-sitter tree-sitter-languages" + ) + return {"nodes": [], "edges": [], "error": hint} except Exception as e: return {"nodes": [], "edges": [], "error": str(e)} diff --git a/tests/test_extract.py b/tests/test_extract.py index 06d371b..5076fe6 100644 --- a/tests/test_extract.py +++ b/tests/test_extract.py @@ -197,3 +197,35 @@ def test_cross_file_calls_skip_ambiguous_duplicate_labels(tmp_path): nodes[e["source"]]["label"] == "run()" and nodes[e["target"]]["label"] == "log()" for e in calls ) + + +def test_extract_generic_surfaces_tree_sitter_version_mismatch_hint(monkeypatch): + """When Language() raises TypeError (e.g. old tree-sitter binding meets a + new tree-sitter API), the error message should point users at the upgrade + path instead of leaving a bare 'missing 1 required positional argument'. + """ + import sys + import types + from graphify.extract import _extract_generic, LanguageConfig + + # Build a fake tree_sitter module whose Language() raises TypeError - + # this is exactly what users see when an older tree-sitter is paired + # with a newer language binding. + fake_ts = types.ModuleType("tree_sitter") + def _raise(*args, **kwargs): + raise TypeError("missing 1 required positional argument: 'name'") + fake_ts.Language = _raise + fake_ts.Parser = None + monkeypatch.setitem(sys.modules, "tree_sitter", fake_ts) + + # Stub the language module so import_module returns something with .language + fake_lang_mod = types.ModuleType("fake_ts_lang") + fake_lang_mod.language = lambda: object() + monkeypatch.setitem(sys.modules, "fake_ts_lang", fake_lang_mod) + + config = LanguageConfig(ts_module="fake_ts_lang", ts_language_fn="language") + result = _extract_generic(Path("dummy.txt"), config) + + assert "error" in result + assert "tree-sitter version mismatch" in result["error"] + assert "pip install --upgrade" in result["error"]