Files
graphify/tests/test_pr10_csharp_overrides.py
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

73 lines
2.8 KiB
Python

from pathlib import Path
import pytest
from tests.test_csharp_interface_dispatch import _INJECTED
from tests.test_csharp_partial_classes import _HALVES, _find
from tests.test_language_overrides import _no_overrides_leak as _no_overrides_leak
from tests.test_language_overrides import _quiet_extract
def _extract_declared(tmp_path, monkeypatch, files, suffix):
monkeypatch.chdir(tmp_path)
(tmp_path / ".graphifyrc").write_text("language.rzr=csharp\n", encoding="utf-8")
paths = []
for name, source in files.items():
path = tmp_path / Path(name).with_suffix(suffix)
path.write_text(source, encoding="utf-8")
paths.append(path)
return _quiet_extract(paths, root=tmp_path, cache_root=tmp_path / ".cache")
@pytest.mark.parametrize("suffix", [".cs", ".rzr"])
def test_partial_halves_share_one_class_and_both_members(tmp_path, monkeypatch, suffix):
result = _extract_declared(tmp_path, monkeypatch, _HALVES, suffix)
classes = [node for node in result["nodes"] if node["label"] == "Foo"]
assert len(classes) == 1, "partial halves must merge into one Foo declaration"
members = {
edge["target"] for edge in result["edges"]
if edge["relation"] == "method" and edge["source"] == classes[0]["id"]
}
assert members == {
_find(result, ".Alpha()", "foo"),
_find(result, ".Beta()", "foo"),
}
@pytest.mark.parametrize("suffix", [".cs", ".rzr"])
def test_derived_receiver_calls_the_inherited_base_method(tmp_path, monkeypatch, suffix):
result = _extract_declared(tmp_path, monkeypatch, {
"S.cs": (
"public class BaseSvc { public bool Ping() => true; }\n"
"public class Derived : BaseSvc { }\n"
"public class User {\n"
" public bool Use(Derived d) { return d.Ping(); }\n"
"}\n"
),
}, suffix)
calls = {
(edge["source"], edge["target"]) for edge in result["edges"]
if edge["relation"] == "calls"
}
assert (
_find(result, ".Use()", "user"),
_find(result, ".Ping()", "basesvc"),
) in calls
@pytest.mark.parametrize("suffix", [".cs", ".rzr"])
def test_injected_interface_dispatches_to_its_implementation(tmp_path, monkeypatch, suffix):
result = _extract_declared(tmp_path, monkeypatch, _INJECTED, suffix)
interface_method = _find(result, ".Build()", "ireport")
implementation_method = _find(result, ".Build()", "report_report")
calls = {
(edge["source"], edge["target"]) for edge in result["edges"]
if edge["relation"] == "calls"
}
dispatch = {
(edge["source"], edge["target"]) for edge in result["edges"]
if edge["relation"] == "dispatches_to"
}
assert (_find(result, ".Go()", "runner"), interface_method) in calls
assert dispatch == {(interface_method, implementation_method)}