Files
graphify/tests/test_language_override_imports.py
T
9c66fd30dc feat(config): declare languages for ambiguous extensions via .graphifyrc (#2961)
An ambiguous extension (`.inc`, `.h`, TS-family, ...) is hard-mapped to one
global default; when that is wrong the file does not error — it parses as the
wrong language and yields a few incidental nodes, so the graph looks populated
while the real symbols are missing. A project can now declare what an extension
means via `language.<ext>=<language|.ext>` lines in `.graphifyrc` (which already
holds `viz_node_limit`). The declaration flows through file classification,
extractor selection, grammar/resolution, imports, watch, and the AST cache
(salt + manifest language field + incremental invalidation), is thread-local
per scan root, and is forwarded to the worker pool. Default detection is
byte-for-byte unchanged when no declaration is present.

Landed from PR #3789 (squashed), which carried @abhay-codes07's original #3075
forward with reviewed follow-ups. Authorship credited to both contributors.

Co-Authored-By: Andre Brait <andrebrait@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-12 12:16:17 +01:00

179 lines
8.0 KiB
Python

"""Declared JS-family suffixes participate in local extensionless imports."""
import pytest
from graphify.extractors.resolution import _resolve_js_module_path
from graphify.rcfile import activate_language_overrides, set_language_overrides
@pytest.fixture(autouse=True)
def reset_overrides():
set_language_overrides(None)
yield
set_language_overrides(None)
@pytest.mark.parametrize("relative", ["dep.tpl", "dep/index.tpl"])
def test_extensionless_import_finds_declared_source_or_directory_index(tmp_path, relative):
(tmp_path / ".graphifyrc").write_text("language.tpl=typescript\n")
source = tmp_path / relative
source.parent.mkdir(parents=True, exist_ok=True)
source.write_text("export function chosen() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_js_module_path("./dep", tmp_path) == source
@pytest.mark.parametrize("directory", [False, True])
def test_native_source_precedence_and_literal_declared_paths_are_preserved(tmp_path, directory):
(tmp_path / ".graphifyrc").write_text("language.tpl=typescript\n")
stem = tmp_path / ("dep/index" if directory else "dep")
stem.parent.mkdir(parents=True, exist_ok=True)
native, declared = stem.with_suffix(".ts"), stem.with_suffix(".tpl")
native.write_text("export function native() {}\n")
declared.write_text("export function declared() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_js_module_path("./dep", tmp_path) == native
assert _resolve_js_module_path(declared) == declared
def test_extensionless_js_lookup_does_not_claim_other_declared_languages(tmp_path):
(tmp_path / ".graphifyrc").write_text("language.tpl=php\n")
(tmp_path / "dep.tpl").write_text("<?php function chosen() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_js_module_path("./dep", tmp_path) == tmp_path / "dep"
def test_import_candidates_ignore_mapping_keys_that_are_not_literal_suffixes(tmp_path):
(tmp_path / ".graphifyrc").write_text("language.tpl/../secret.ts=typescript\n")
(tmp_path / "dep.tpl").mkdir()
(tmp_path / "secret.ts").write_text("export function unrelated() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_js_module_path("./dep", tmp_path) == tmp_path / "dep"
@pytest.mark.parametrize("layout", ["module", "package", "reexport", "submodule", "namespace"])
def test_python_imports_bind_declared_providers_instead_of_same_named_alternatives(tmp_path, layout):
from graphify.extract import extract
(tmp_path / ".graphifyrc").write_text("language.pyx=python\n")
sources = {"other.pyx": "def chosen(): return 2\n"}
statement = "from one import chosen as local\ndef caller(): return local()\n"
if layout == "module":
target = "one.pyx"
elif layout == "package":
target = "one/__init__.pyx"
elif layout == "reexport":
target = "one/body.pyx"
sources["one/__init__.pyx"] = "from .body import chosen\n"
else:
target = "pkg/one.pyx"
statement = "from pkg import one\ndef caller(): return one.chosen()\n"
if layout == "submodule":
sources["pkg/__init__.pyx"] = ""
sources[target] = "def chosen(): return 1\n"
sources["caller.pyx"] = statement
paths = []
for relative, content in sources.items():
path = tmp_path / relative
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
paths.append(path)
result = extract(paths, root=tmp_path, cache_root=tmp_path)
nodes = {node["id"]: node for node in result["nodes"]}
targets = {(nodes[edge["target"]]["label"], nodes[edge["target"]].get("source_file"))
for edge in result["edges"] if edge["relation"] == "calls"
and nodes.get(edge["source"], {}).get("label") == "caller()"}
assert targets == {("chosen()", target)}
def test_python_native_module_and_package_precedence_is_preserved(tmp_path):
from graphify.extractors.resolution import _resolve_python_module_path
(tmp_path / ".graphifyrc").write_text("language.pyx=python\n")
(tmp_path / "one").mkdir()
(tmp_path / "one/__init__.pyx").write_text("def declared(): pass\n")
native = tmp_path / "one.py"
native.write_text("def native(): pass\n")
activate_language_overrides(tmp_path)
caller = tmp_path / "caller.py"
assert _resolve_python_module_path("one", caller, tmp_path, 0) == native
native_package = tmp_path / "one/__init__.py"
native_package.write_text("def package(): pass\n")
assert _resolve_python_module_path("one", caller, tmp_path, 0) == native_package
def test_python_lookup_does_not_claim_another_declared_language(tmp_path):
from graphify.extractors.resolution import _resolve_python_module_path
(tmp_path / ".graphifyrc").write_text("language.tpl=php\n")
(tmp_path / "one.tpl").write_text("<?php function chosen() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_python_module_path("one", tmp_path / "caller.py", tmp_path, 0) is None
def test_declared_native_suffix_can_supply_an_additional_directory_index(tmp_path):
(tmp_path / ".graphifyrc").write_text("language.cjs=typescript\n")
source = tmp_path / "dep/index.cjs"
source.parent.mkdir()
source.write_text("export function chosen() {}\n")
activate_language_overrides(tmp_path)
assert _resolve_js_module_path("./dep", tmp_path) == source
@pytest.mark.parametrize("suffix", [".py", ".pyx"])
@pytest.mark.parametrize("namespace", [False, True])
def test_declared_packages_preserve_absolute_and_explicit_relative_import_scope(tmp_path, suffix, namespace):
from graphify.extract import extract
(tmp_path / ".graphifyrc").write_text("language.pyx=python\n")
package = tmp_path / "src/pkg"
package.mkdir(parents=True)
init = package / f"__init__{suffix}"
init.write_text("")
target = package / (f"utilities/helper{suffix}" if namespace else f"helper{suffix}")
target.parent.mkdir(exist_ok=True)
target.write_text("def chosen(): return 1\n")
caller = package / f"caller{suffix}"
for relative in (True, False):
prefix = "." if relative else ""
statement = (
f"from {prefix}utilities import helper\ndef caller(): return helper.chosen()\n"
if namespace else
f"from {prefix}helper import chosen as local\ndef caller(): return local()\n"
)
caller.write_text(statement)
result = extract([init, target, caller], root=tmp_path, cache_root=tmp_path)
nodes = {node["id"]: node for node in result["nodes"]}
targets = {nodes[edge["target"]].get("source_file") for edge in result["edges"]
if edge["relation"] == "calls" and nodes.get(edge["source"], {}).get("label") == "caller()"}
assert targets == ({target.relative_to(tmp_path).as_posix()} if relative else set())
def test_declared_package_initializer_is_not_a_namespace_package(tmp_path):
from graphify.extractors.resolution import _resolve_python_namespace_dir
(tmp_path / ".graphifyrc").write_text("language.pyx=python\n")
package = tmp_path / "pkg"
package.mkdir()
(package / "__init__.pyx").write_text("def chosen(): return 1\n")
activate_language_overrides(tmp_path)
assert _resolve_python_namespace_dir("pkg", tmp_path / "caller.py", tmp_path, 0) is None
def test_import_of_declared_src_layout_package_targets_its_real_initializer(tmp_path):
from graphify.extract import extract
(tmp_path / ".graphifyrc").write_text("language.pyx=python\n")
package = tmp_path / "src/pkg"
package.mkdir(parents=True)
init = package / "__init__.pyx"
init.write_text("def chosen(): return 1\n")
caller = tmp_path / "src/caller.pyx"
caller.write_text("import pkg\n")
result = extract([init, caller], root=tmp_path, cache_root=tmp_path)
nodes = {node["id"]: node for node in result["nodes"]}
targets = {nodes.get(edge["target"], {}).get("source_file") for edge in result["edges"]
if edge["relation"] in {"imports", "imports_from"}
and nodes.get(edge["source"], {}).get("source_file") == "src/caller.pyx"}
assert targets == {"src/pkg/__init__.pyx"}