Skip to content

Commit 8949e4f

Browse files
committed
fix(scala): normalize qualified heritage names
1 parent 62b1d43 commit 8949e4f

2 files changed

Lines changed: 61 additions & 11 deletions

File tree

‎graphify/extractors/engine.py‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2797,18 +2797,39 @@ def _emit_java_parent_type(type_node, rel: str, at_line: int) -> None:
27972797
break
27982798
if extend is not None:
27992799
bases: list[tuple[str, int]] = []
2800-
for c in extend.children:
2801-
if c.type == "type_identifier":
2802-
bases.append((_read_text(c, source), c.start_point[0] + 1))
2803-
elif c.type == "generic_type":
2804-
base = c.child_by_field_name("type")
2800+
2801+
def scala_base_name(type_node) -> str | None:
2802+
if type_node.type == "type_identifier":
2803+
return _read_text(type_node, source)
2804+
if type_node.type == "stable_type_identifier":
2805+
tail = next(
2806+
(
2807+
child
2808+
for child in reversed(type_node.children)
2809+
if child.type in ("type_identifier", "identifier")
2810+
),
2811+
None,
2812+
)
2813+
return _read_text(tail, source) if tail is not None else None
2814+
if type_node.type == "generic_type":
2815+
base = type_node.child_by_field_name("type")
28052816
if base is None:
2806-
for sc in c.children:
2807-
if sc.type == "type_identifier":
2808-
base = sc
2809-
break
2810-
if base is not None:
2811-
bases.append((_read_text(base, source), c.start_point[0] + 1))
2817+
base = next(
2818+
(
2819+
child
2820+
for child in type_node.children
2821+
if child.type
2822+
in ("type_identifier", "stable_type_identifier")
2823+
),
2824+
None,
2825+
)
2826+
return scala_base_name(base) if base is not None else None
2827+
return None
2828+
2829+
for c in extend.children:
2830+
base_name = scala_base_name(c)
2831+
if base_name is not None:
2832+
bases.append((base_name, c.start_point[0] + 1))
28122833
for idx, (base_name, base_line) in enumerate(bases):
28132834
rel = "inherits" if idx == 0 else "mixes_in"
28142835
base_nid = ensure_named_node(base_name, base_line)

‎tests/test_languages.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,35 @@ def test_scala_splits_inherits_and_mixes_in():
745745
assert ("HttpClient", "Loggable") in _edge_labels(r, "mixes_in")
746746

747747

748+
def test_scala_qualified_extends_uses_tail_name(tmp_path):
749+
"""A qualified base like `pkg.Base` is a `stable_type_identifier` node in the
750+
extends_clause. The handler only matched `type_identifier`/`generic_type`, so
751+
qualified extends/with clauses were dropped. Resolve them to the tail type
752+
name (`Base`, `Trait1`), not the package path.
753+
"""
754+
f = tmp_path / "foo.scala"
755+
f.write_text(
756+
"class Foo extends pkg.Base with other.Trait1\n"
757+
"class Generic extends pkg.Base[Int] with other.Trait1[String]\n"
758+
"class Constructed extends outer.pkg.Base[Int](42) "
759+
"with a.b.Trait1[String]\n"
760+
)
761+
r = extract_scala(f)
762+
inherits = _edge_labels(r, "inherits")
763+
mixes_in = _edge_labels(r, "mixes_in")
764+
assert ("Foo", "Base") in inherits
765+
assert ("Foo", "Trait1") in mixes_in
766+
assert ("Generic", "Base") in inherits
767+
assert ("Generic", "Trait1") in mixes_in
768+
assert ("Constructed", "Base") in inherits
769+
assert ("Constructed", "Trait1") in mixes_in
770+
# Package qualifiers and generic arguments are not parent type names.
771+
assert ("Generic", "pkg.Base") not in inherits
772+
assert ("Generic", "other.Trait1") not in mixes_in
773+
assert ("Constructed", "Int") not in inherits
774+
assert ("Constructed", "String") not in mixes_in
775+
776+
748777
def test_scala_constructor_parameter_field_context():
749778
r = extract_scala(FIXTURES / "sample.scala")
750779
assert ("HttpClient", "Config") in _edge_labels(r, "references", "field")

0 commit comments

Comments
 (0)