fix(cpp): guard empty-normalize collapse for punctuation-only test names (#2594)

The #2751 recovery minted node ids via _make_id(stem, name); a punctuation-only
name (TEST_CASE("***")) normalizes to empty, collapsing onto the bare file-stem
id — colliding with the file namespace and swallowing every later such test
under one id via seen_ids (#1899). Detect the collapse and fall back to a stable
line-positional id so each stays distinct. Adds a regression test (also covers
SCENARIO) and the CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
safishamsi
2026-08-15 17:24:35 +01:00
co-authored by Claude Opus 4.8
parent 54b3b78955
commit e6b0efb66b
3 changed files with 33 additions and 0 deletions
+1
View File
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## 0.9.44 (unreleased)
- Fix: doctest/Catch2 string-named test cases (`TEST_CASE("...")`, `SCENARIO`, `TEST_CASE_TEMPLATE`), which tree-sitter-cpp drops as ERROR nodes, are recovered as callable nodes contained by the file (#2594, thanks @ousamabenyounes); a punctuation-only test name gets a distinct line-positional id instead of collapsing onto the file-stem id.
- Fix: `graphify affected` resolves an absolute-path seed against the repo root derived from the graph's own location instead of the current working directory, so a blast-radius query with an absolute seed run from anywhere (an editor, a script) no longer silently returns nothing; a seed outside the root still misses cleanly (#2706, thanks @ousamabenyounes).
- Fix: a lazy CommonJS `require(...)` inside a function body (the idiom for breaking circular dependencies) now emits the same `imports_from`/`imports` dependency edges as a top-level require, attributed to the enclosing function, instead of being silently dropped; a dynamic `require(variable)` is still skipped (#2700, thanks @rajanpanth).
- Fix: a JS/TS identifier bound by an import whose target resolves outside the scanned corpus (e.g. a `lucide-react` icon) is now shadowed, so using it as a value no longer fabricates an INFERRED `indirect_call` onto an unrelated same-named callable elsewhere in the corpus; a relative/in-corpus import still resolves to its real target (#2757, thanks @phudayyy).
+7
View File
@@ -1910,6 +1910,11 @@ def _augment_cpp_string_tests(path: Path, result: dict) -> dict:
str_path = str(path)
stem = _file_stem(path)
file_nid = _make_id(str_path)
# A test name that is all punctuation (TEST_CASE("***")) normalizes to empty,
# so _make_id(stem, name) collapses onto this bare-stem id — colliding with
# the file's namespace and silently swallowing every later such test under
# one id (#1899). Detect that collapse and fall back to a line-positional id.
stem_collapse_id = _make_id(stem)
nodes = result.setdefault("nodes", [])
edges = result.setdefault("edges", [])
seen_ids = {n.get("id") for n in nodes}
@@ -1918,6 +1923,8 @@ def _augment_cpp_string_tests(path: Path, result: dict) -> dict:
test_name = m.group(1)
line = source.count("\n", 0, m.start()) + 1
test_nid = _make_id(stem, test_name)
if test_nid == stem_collapse_id:
test_nid = _make_id(stem, "test", f"L{line}")
if test_nid in seen_ids:
continue
seen_ids.add(test_nid)
+25
View File
@@ -215,6 +215,31 @@ def test_cpp_recovers_doctest_string_named_test_cases():
}
assert test_ids and test_ids <= contained
def test_cpp_string_tests_punctuation_only_names_stay_distinct(tmp_path):
"""A punctuation-only test name normalizes to empty, so a naive
``_make_id(stem, name)`` collapses onto the bare file-stem id — colliding
with the file namespace and swallowing every later such test under one id
(#1899). The guard gives each a distinct line-positional id."""
f = tmp_path / "punct_tests.cpp"
f.write_text(
'TEST_CASE("***") { CHECK(1); }\n'
'TEST_CASE("...") { CHECK(2); }\n'
'SCENARIO("boots up") { REQUIRE(1); }\n',
encoding="utf-8",
)
r = extract_cpp(f)
from graphify.extract import _make_id, _file_stem
stem_collapse = _make_id(_file_stem(f))
tests = [n for n in r["nodes"] if n["label"].startswith('"')]
ids = [n["id"] for n in tests]
# both punctuation-only cases recovered, plus the SCENARIO macro
assert '"***"' in {n["label"] for n in tests}
assert '"..."' in {n["label"] for n in tests}
assert '"boots up"' in {n["label"] for n in tests}
assert len(set(ids)) == len(ids) # all distinct
assert all(i != stem_collapse for i in ids) # none squats on the file stem
def test_cpp_finds_includes():
r = extract_cpp(FIXTURES / "sample.cpp")
assert "imports" in _relations(r)