Skip to content
Closed
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
21 changes: 16 additions & 5 deletions graphify/affected.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import unicodedata
from collections import deque
from dataclasses import dataclass
from pathlib import Path
Expand All @@ -8,6 +9,16 @@
import networkx as nx


def _normalize(text: str) -> str:
"""Return *text* in a canonical form so NFD and NFC compare equal.

macOS stores filenames in NFD; Python string literals and most input
methods produce NFC. Comparing the two without normalization silently
fails for accented Latin, Japanese with dakuten, and Korean Hangul.
"""
return unicodedata.normalize("NFC", text)


DEFAULT_AFFECTED_RELATIONS = (
"calls",
"references",
Expand Down Expand Up @@ -52,11 +63,11 @@ def _bare_name(label: str) -> str:
def resolve_seed(graph: nx.Graph, query: str) -> str | None:
if query in graph:
return query
query_lower = query.lower()
query_lower = _normalize(query).lower()
exact_label_matches = [
str(node_id)
for node_id, data in graph.nodes(data=True)
if str(data.get("label", "")).lower() == query_lower
if _normalize(str(data.get("label", ""))).lower() == query_lower
]
if len(exact_label_matches) == 1:
return exact_label_matches[0]
Expand All @@ -67,21 +78,21 @@ def resolve_seed(graph: nx.Graph, query: str) -> str | None:
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 _bare_name(_normalize(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)
if str(data.get("source_file", "")).lower() == query_lower
if _normalize(str(data.get("source_file", ""))).lower() == query_lower
]
if len(exact_source_matches) == 1:
return exact_source_matches[0]
contains_matches = [
str(node_id)
for node_id, data in graph.nodes(data=True)
if query_lower in str(data.get("label", "")).lower()
if query_lower in _normalize(str(data.get("label", ""))).lower()
]
if len(contains_matches) == 1:
return contains_matches[0]
Expand Down
27 changes: 27 additions & 0 deletions tests/test_affected.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Unit tests for graphify.affected.resolve_seed.

Covers Unicode normalization (NFC) so accented and CJK labels resolve
regardless of whether the query comes in NFC (Python source, most input
methods) or NFD (macOS filenames, some IMEs).
"""
from __future__ import annotations

import unicodedata

import networkx as nx

from graphify.affected import resolve_seed


def _graph_with(label: str) -> nx.Graph:
"""Build a one-node graph whose label is stored exactly as given."""
g = nx.DiGraph()
g.add_node("n1", label=label, source_file="pkg/foo.py", source_location="L1")
return g


def test_resolve_seed_matches_nfd_query_against_nfc_label() -> None:
label_nfc = "Auditoría"
query_nfd = unicodedata.normalize("NFD", label_nfc)
graph = _graph_with(label_nfc)
assert resolve_seed(graph, query_nfd) == "n1"