mirror of
https://github.com/safishamsi/graphify.git
synced 2026-07-12 18:37:12 +00:00
86ecb769b6
Resolve Ruby `obj.method()` calls by the inferred type of the receiver instead of by globally-unique method name. `p = Processor.new; p.run` now emits a `calls` edge to `Processor#run` and survives name collisions with unrelated `Worker#run` definitions, where the old name-based match either resolved by luck or dropped the edge as ambiguous. Introduces graphify/resolver_registry.py, a behavior-identical formalization of the existing tail-of-extract() language resolution passes (Swift #1356, Python #1446 become registered entries), and graphify/ruby_resolution.py, its first new consumer. Receiver type is inferred only from unambiguous local `var = ClassName.new` bindings; ambiguous or unknown receivers resolve to nothing (no false positives). Note: Ruby member calls are now excluded from name-based cross-file resolution and resolved by inferred type only. This is an intentional precision-over-recall change scoped to Ruby: a cross-file `var.method` whose receiver type cannot be proven from a local `X.new` binding no longer resolves by name-luck (it produces no edge rather than a possibly wrong one), matching the project's confidence model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""Tests for the language resolver registry (graphify.resolver_registry).
|
|
|
|
The registry formalizes the previously hand-wired, suffix-gated cross-file
|
|
resolution passes. These tests pin its contract so future languages can be
|
|
registered with confidence: gating by suffix, in-order execution, in-place
|
|
mutation, and fault isolation (a failing pass logs and is skipped, never
|
|
aborting the build or blocking later passes).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from graphify.resolver_registry import (
|
|
LanguageResolver,
|
|
registered_resolvers,
|
|
run_language_resolvers,
|
|
)
|
|
|
|
|
|
def _make_resolver(name: str, suffix: str, log: list[str]) -> LanguageResolver:
|
|
def _resolve(per_file, all_nodes, all_edges):
|
|
log.append(name)
|
|
return LanguageResolver(name, frozenset({suffix}), _resolve)
|
|
|
|
|
|
def test_default_registry_contains_swift_then_python() -> None:
|
|
# Importing extract registers its resolvers into the shared registry. Order
|
|
# matters: it preserves the prior inlined wiring (Swift before Python).
|
|
import graphify.extract # noqa: F401 (registers resolvers on import)
|
|
|
|
names = [r.name for r in registered_resolvers()]
|
|
assert "swift_member_calls" in names
|
|
assert "python_member_calls" in names
|
|
assert names.index("swift_member_calls") < names.index("python_member_calls")
|
|
|
|
|
|
def test_resolver_runs_only_when_suffix_present() -> None:
|
|
log: list[str] = []
|
|
resolvers = [_make_resolver("ruby", ".rb", log), _make_resolver("go", ".go", log)]
|
|
run_language_resolvers([Path("a.rb")], [], [], [], resolvers=resolvers)
|
|
assert log == ["ruby"] # go skipped: no .go file present
|
|
|
|
|
|
def test_resolvers_run_in_given_order() -> None:
|
|
log: list[str] = []
|
|
resolvers = [_make_resolver("first", ".rb", log), _make_resolver("second", ".rb", log)]
|
|
run_language_resolvers([Path("a.rb")], [], [], [], resolvers=resolvers)
|
|
assert log == ["first", "second"]
|
|
|
|
|
|
def test_failing_resolver_is_isolated() -> None:
|
|
log: list[str] = []
|
|
|
|
def _boom(per_file, all_nodes, all_edges):
|
|
raise RuntimeError("resolver blew up")
|
|
|
|
resolvers = [
|
|
LanguageResolver("boom", frozenset({".rb"}), _boom),
|
|
_make_resolver("after", ".rb", log),
|
|
]
|
|
# Must not raise, and the later resolver still runs.
|
|
run_language_resolvers([Path("a.rb")], [], [], [], resolvers=resolvers)
|
|
assert log == ["after"]
|
|
|
|
|
|
def test_resolver_mutates_edges_in_place() -> None:
|
|
def _add_edge(per_file, all_nodes, all_edges):
|
|
all_edges.append({"source": "x", "target": "y", "relation": "calls"})
|
|
|
|
resolvers = [LanguageResolver("adder", frozenset({".rb"}), _add_edge)]
|
|
edges: list[dict] = []
|
|
run_language_resolvers([Path("a.rb")], [], [], edges, resolvers=resolvers)
|
|
assert edges == [{"source": "x", "target": "y", "relation": "calls"}]
|