fix(groovy): emit inherits/implements edges for extends/implements

tree-sitter-groovy exposes inheritance via the same `superclass` and
`interfaces`/`type_list` fields as tree-sitter-java, but the inheritance-
emitting block in `_extract_generic` was gated on
`ts_module == "tree_sitter_java"`. Groovy was the only class-based JVM
language in the file with no inheritance handler, so every Groovy
`extends`/`implements` was silently dropped (contains/methods/imports/calls
were unaffected).

Widen the gate to include `tree_sitter_groovy`; the existing
`_emit_java_parent_type` path handles the identical node shapes verbatim.
Adds a base class + interface to the Groovy fixture and two regression
tests (extends -> inherits, implements -> implements).

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 a19b9e90ec
commit 64a6093376
3 changed files with 44 additions and 1 deletions
+1 -1
View File
@@ -3513,7 +3513,7 @@ def _extract_generic(
context="generic_arg", metadata=metadata)
# Java-specific: extends (superclass) / implements (interfaces) / interface-extends
if config.ts_module == "tree_sitter_java":
if config.ts_module in ("tree_sitter_java", "tree_sitter_groovy"):
def _emit_java_parent(base_name: str, rel: str, at_line: int) -> None:
if not base_name:
return
+14
View File
@@ -19,3 +19,17 @@ class SampleService {
processor.reset()
}
}
interface Resettable {
void reset()
}
class ExtendedService extends SampleService implements Resettable {
ExtendedService(Processor processor) {
super(processor)
}
void reset() {
// no-op
}
}
+29
View File
@@ -2195,6 +2195,35 @@ def test_groovy_no_dangling_edges():
assert e["source"] in node_ids
def test_groovy_extends_edge():
"""`class X extends Base` must emit an inherits edge.
tree-sitter-groovy exposes inheritance via the same `superclass` field as
tree-sitter-java, but the inheritance handler was gated to Java only, so
Groovy extends/implements were silently dropped.
"""
r = extract_groovy(FIXTURES / "sample.groovy")
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
found = any(
"ExtendedService" in node_by_id.get(e["source"], "")
and "SampleService" in node_by_id.get(e["target"], "")
for e in r["edges"] if e["relation"] == "inherits"
)
assert found, "ExtendedService should have inherits edge to SampleService"
def test_groovy_implements_edge():
"""`class X implements Iface` must emit an implements edge."""
r = extract_groovy(FIXTURES / "sample.groovy")
node_by_id = {n["id"]: n["label"] for n in r["nodes"]}
found = any(
"ExtendedService" in node_by_id.get(e["source"], "")
and "Resettable" in node_by_id.get(e["target"], "")
for e in r["edges"] if e["relation"] == "implements"
)
assert found, "ExtendedService should have implements edge to Resettable"
def test_groovy_spock_finds_class():
r = extract_groovy(FIXTURES / "sample_spock.groovy")
assert any("SampleSpec" in l for l in _labels(r))