fix(ruby): emit inherits edge for class superclass

`class Dog < Animal` exposes the base in the `superclass` field, but the
inheritance handler in `_extract_generic` had branches for
java/kotlin/c#/scala/cpp/php/swift/python and none for Ruby, so every Ruby
`inherits` edge was silently dropped (contains/methods/calls unaffected).

Add a Ruby branch that reads the `superclass` field, handling both a bare
`constant` (`< Animal`) and a `scope_resolution` (`< Foo::Bar` -> Bar).
Adds a subclass to the Ruby fixture and a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hibinm
2026-07-01 10:47:10 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent 879c05894d
commit a19b9e90ec
3 changed files with 53 additions and 0 deletions
+31
View File
@@ -3426,6 +3426,37 @@ def _extract_generic(
add_edge(class_nid, target, "references", line,
context="generic_arg")
# Ruby: `class Dog < Animal` puts the base class in the `superclass`
# field (a `<` token followed by a constant or scope_resolution).
# There was no Ruby branch, so every Ruby inherits edge was dropped.
if config.ts_module == "tree_sitter_ruby":
sup = node.child_by_field_name("superclass")
if sup is not None:
base = ""
for sub in sup.children:
if sub.type == "constant":
base = _read_text(sub, source)
break
if sub.type == "scope_resolution":
consts = [c for c in sub.children if c.type == "constant"]
if consts:
base = _read_text(consts[-1], source)
break
if base:
base_nid = _make_id(stem, base)
if base_nid not in seen_ids:
base_nid = _make_id(base)
if base_nid not in seen_ids:
nodes.append({
"id": base_nid,
"label": base,
"file_type": "code",
"source_file": "",
"source_location": "",
})
seen_ids.add(base_nid)
add_edge(class_nid, base_nid, "inherits", line)
# C#-specific: inheritance / interface implementation via base_list
if config.ts_module == "tree_sitter_c_sharp":
csharp_type_params = _csharp_type_parameters_in_scope(node, source)
+6
View File
@@ -22,6 +22,12 @@ class ApiClient
end
end
class TimeoutApiClient < ApiClient
def fetch(path, method)
super
end
end
def parse_response(raw)
JSON.parse(raw)
end
+16
View File
@@ -299,6 +299,22 @@ def test_ruby_finds_function():
assert any("parse_response" in l for l in _labels(r))
def test_ruby_inherits_edge():
"""`class Sub < Base` must emit an inherits edge.
Ruby exposes the base class in the `superclass` field, but there was no
Ruby branch in the inheritance handler, so the edge was silently dropped.
"""
r = extract_ruby(FIXTURES / "sample.rb")
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
found = any(
"TimeoutApiClient" in node_by_id.get(e["source"], "")
and node_by_id.get(e["target"], "") == "ApiClient"
for e in r["edges"] if e["relation"] == "inherits"
)
assert found, "TimeoutApiClient should have inherits edge to ApiClient"
# ── C# ───────────────────────────────────────────────────────────────────────
def test_csharp_no_error():