Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions graphify/affected.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ def _format_location(data: dict) -> str:
return str(source_file)


def _bare_name(label: str) -> str:
"""Lowercased label with the callable decoration (trailing "()") removed."""
label = label.lower()
return label[:-2] if label.endswith("()") else label


def resolve_seed(graph: nx.Graph, query: str) -> str | None:
if query in graph:
return query
Expand All @@ -54,6 +60,17 @@ def resolve_seed(graph: nx.Graph, query: str) -> str | None:
]
if len(exact_label_matches) == 1:
return exact_label_matches[0]
# Callable labels are decorated ("name()"), so a bare "name" query falls
# through exact matching and then ties with any "name*" sibling in the
# contains pass. Match on the undecorated name before giving up.
query_bare = _bare_name(query_lower)
bare_name_matches = [
str(node_id)
for node_id, data in graph.nodes(data=True)
if _bare_name(str(data.get("label", ""))) == query_bare
]
if len(bare_name_matches) == 1:
return bare_name_matches[0]
exact_source_matches = [
str(node_id)
for node_id, data in graph.nodes(data=True)
Expand Down
31 changes: 31 additions & 0 deletions tests/test_affected_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,34 @@ def test_affected_cli_forces_directed_on_undirected_graph(monkeypatch, tmp_path,
assert "calls" in out
# B is the query node, not an affected node, and the result is not empty.
assert "No affected nodes found." not in out


def test_resolve_seed_bare_name_matches_callable_label():
from graphify.affected import resolve_seed

graph = nx.DiGraph()
graph.add_node("a", label="classifyProperty()", source_file="pkg/entity.py")
graph.add_node("b", label="classifyPropertySafe()", source_file="app/context.py")

assert resolve_seed(graph, "classifyProperty") == "a"
assert resolve_seed(graph, "classifyPropertySafe") == "b"


def test_resolve_seed_decorated_query_matches_bare_label():
from graphify.affected import resolve_seed

graph = nx.DiGraph()
graph.add_node("a", label="Foo", source_file="pkg/foo.py")
graph.add_node("b", label="FooBar", source_file="pkg/foobar.py")

assert resolve_seed(graph, "Foo()") == "a"


def test_resolve_seed_bare_name_tie_still_returns_none():
from graphify.affected import resolve_seed

graph = nx.DiGraph()
graph.add_node("a", label="dup()", source_file="pkg/one.py")
graph.add_node("b", label="dup()", source_file="pkg/two.py")

assert resolve_seed(graph, "dup") is None