fix node label lookup normalization (#1353)

Fixes #1338 (Unicode NFD/NFC): serve._find_node now matches tokenized labels; affected.resolve_seed NFC-normalizes + casefolds. Reviewed: full suite 2087 passed, CLI smoke clean, no regressions. Thanks @balloon72.
This commit is contained in:
balloon72
2026-06-17 10:29:43 +01:00
committed by GitHub
parent be3dcfca08
commit d885833112
4 changed files with 47 additions and 8 deletions
+10 -5
View File
@@ -4,6 +4,7 @@ from collections import deque
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
import unicodedata
import networkx as nx
@@ -45,18 +46,22 @@ def _format_location(data: dict) -> str:
def _bare_name(label: str) -> str:
"""Lowercased label with the callable decoration (trailing "()") removed."""
label = label.lower()
label = _normalize_label(label)
return label[:-2] if label.endswith("()") else label
def _normalize_label(label: str) -> str:
return unicodedata.normalize("NFC", label).casefold()
def resolve_seed(graph: nx.Graph, query: str) -> str | None:
if query in graph:
return query
query_lower = query.lower()
query_lower = _normalize_label(query)
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_label(str(data.get("label", ""))) == query_lower
]
if len(exact_label_matches) == 1:
return exact_label_matches[0]
@@ -74,14 +79,14 @@ def resolve_seed(graph: nx.Graph, query: str) -> str | None:
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_label(str(data.get("source_file", ""))) == 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_label(str(data.get("label", "")))
]
if len(contains_matches) == 1:
return contains_matches[0]
+9 -3
View File
@@ -464,12 +464,18 @@ def _find_node(G: nx.Graph, label: str) -> list[str]:
for nid, d in G.nodes(data=True):
norm_label = d.get("norm_label") or _strip_diacritics(d.get("label") or "").lower()
bare_label = norm_label.rstrip("()")
label_tokens = " ".join(_search_tokens(d.get("label") or ""))
nid_lower = nid.lower()
if term == norm_label or term == bare_label or term == nid_lower:
if term == norm_label or term == bare_label or term == label_tokens or term == nid_lower:
exact.append(nid)
elif norm_label.startswith(term) or bare_label.startswith(term) or nid_lower.startswith(term):
elif (
norm_label.startswith(term)
or bare_label.startswith(term)
or label_tokens.startswith(term)
or nid_lower.startswith(term)
):
prefix.append(nid)
elif term in norm_label:
elif term in norm_label or term in label_tokens:
substring.append(nid)
return exact + prefix + substring
+21
View File
@@ -145,6 +145,27 @@ def test_resolve_seed_decorated_query_matches_bare_label():
assert resolve_seed(graph, "Foo()") == "a"
def test_resolve_seed_matches_unicode_normalized_label():
import unicodedata
from graphify.affected import resolve_seed
graph = nx.DiGraph()
graph.add_node("a", label="Auditoría", source_file="pkg/auditoria.py")
assert resolve_seed(graph, unicodedata.normalize("NFD", "Auditoría")) == "a"
def test_resolve_seed_preserves_distinct_accents():
from graphify.affected import resolve_seed
graph = nx.DiGraph()
graph.add_node("a", label="resume", source_file="pkg/resume.py")
graph.add_node("b", label="résumé", source_file="pkg/resume_accented.py")
assert resolve_seed(graph, "resume") == "a"
def test_resolve_seed_bare_name_tie_still_returns_none():
from graphify.affected import resolve_seed
+7
View File
@@ -123,6 +123,13 @@ def test_find_node_ignores_trailing_punctuation():
assert _find_node(G, "extract?") == ["n1"]
def test_find_node_matches_full_punctuated_unicode_label():
G = nx.Graph()
G.add_node("n1", label="Skill /auditar — Auditoría inquisitiva de enlaces")
assert _find_node(G, "Skill /auditar — Auditoría inquisitiva de enlaces") == ["n1"]
def test_query_terms_strips_search_punctuation():
assert _query_terms("what calls extract?") == ["what", "calls", "extract"]