diff --git a/graphify/affected.py b/graphify/affected.py index 10c63187..80bdc204 100644 --- a/graphify/affected.py +++ b/graphify/affected.py @@ -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 @@ -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) diff --git a/tests/test_affected_cli.py b/tests/test_affected_cli.py index f2a4e554..40e0d68e 100644 --- a/tests/test_affected_cli.py +++ b/tests/test_affected_cli.py @@ -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