Skip to content

fix(groovy): emit ClassRelation for interface inheritance in both engines#1158

Open
carlos-alm wants to merge 1 commit into
mainfrom
fix/1115-groovy-interface-inheritance
Open

fix(groovy): emit ClassRelation for interface inheritance in both engines#1158
carlos-alm wants to merge 1 commit into
mainfrom
fix/1115-groovy-interface-inheritance

Conversation

@carlos-alm
Copy link
Copy Markdown
Contributor

Summary

  • interface X extends Y, Z produced no class-graph edges because both Groovy extractors pushed a single Definition for the interface but never inspected the unnamed extends_interfaces child.
  • The Rust handler now reuses the existing collect_interfaces helper (which already recurses into type_list); the TS handler gains an equivalent collectGroovyParentInterfaces helper that mirrors the same traversal.
  • Both engines emit ClassRelation { name: X, implements: Y, line } for each parent interface — consistent with the existing collect_interfaces helper, with class-implements behavior, and with the downstream IMPLEMENTS_TARGET_KINDS filter that already accepts interface targets.

Notes

  • tree-sitter-groovy 0.1.x exposes parent interfaces as an unnamed extends_interfaces child of interface_declaration (not a field), distinct from class declarations which use the interfaces field that wraps super_interfaces → type_list.
  • This PR fixes the extraction layer only. The downstream HIERARCHY_SOURCE_KINDS set in src/domain/graph/builder/stages/build-edges.ts does not currently include interface, so emitted relations still won't materialize as graph edges until that filter is extended — that's a broader cross-language change worth tracking separately.

Closes #1115

Test plan

  • cargo test -p codegraph-core --lib extractors::groovy — new extracts_interface_inheritance test passes alongside existing 8 tests
  • npx vitest run tests/parsers/groovy.test.ts — new extracts interface inheritance (extends_interfaces) test passes alongside existing 6 tests
  • npx biome check src/extractors/groovy.ts tests/parsers/groovy.test.ts — clean

…ines

`interface X extends Y, Z` produced no class-graph edges because both
extractors pushed only a Definition for the interface and never inspected
the unnamed `extends_interfaces` child. The Rust handler now reuses
`collect_interfaces` (which already recurses into `type_list`); the TS
handler gains an equivalent helper that mirrors the same traversal.

Closes #1115
@claude
Copy link
Copy Markdown

claude Bot commented May 18, 2026

Claude finished @carlos-alm's task —— View job


Reviewing PR: fix(groovy): emit ClassRelation for interface inheritance in both engines

Todo List

  • Phase 1: Root cause analysis and approach justification
  • Phase 2: Check for configuration changes
  • Phase 3: Verify test integrity
  • Phase 4: Code quality review
  • Phase 5: Backlog compliance assessment
  • Final: Provide structured review feedback

Root Cause Analysis

Problem Being Solved: interface X extends Y, Z was producing no class-graph edges because both Groovy extractors only emitted a Definition for the interface but never inspected the unnamed extends_interfaces child that tree-sitter-groovy uses to represent parent interfaces.

Why This Approach:

  • Correct fix at source: Addresses the extraction layer where the bug actually occurs
  • Consistent pattern: Rust handler reuses existing collect_interfaces helper; TypeScript gets equivalent collectGroovyParentInterfaces helper
  • Grammar-aware: Properly handles tree-sitter-groovy's distinction between interface inheritance (extends_interfaces unnamed child) vs class implements (interfaces field)
  • Engine parity: Both extractors now emit identical ClassRelation structures

Risk Assessment: LOW - Purely additive change to extraction logic, no behavioral modifications to existing functionality.

Backlog Compliance

  • Zero-dep: ✓ — no new runtime dependencies, builds on existing tree-sitter infrastructure
  • Foundation-aligned: ✓ — improves accuracy of dependency graph extraction, core to the tool's mission
  • Problem-fit: 4/5 — directly prevents AI hallucination of interface relationships by ensuring accurate extraction
  • Breaking: No — purely additive extraction, no API/output format changes
  • Tier: 1 (zero-dep ✓ + foundation-aligned ✓)

Code Quality Assessment

Implementation Strengths

  1. Engine Parity: Both Rust and TypeScript implementations follow identical logic patterns
  2. Helper Reuse: Rust cleverly reuses existing collect_interfaces that already handles type_list recursion
  3. Grammar Correctness: Proper distinction between extends_interfaces (interface inheritance) vs interfaces field (class implements)
  4. Test Coverage: Comprehensive tests in both engines validate the fix end-to-end

Technical Analysis

Rust Implementation (groovy.rs:173-184):

for i in 0..node.child_count() {
    let Some(child) = node.child(i) else { continue };
    if child.kind() == "extends_interfaces" {
        collect_interfaces(&child, &iface_name, source, symbols);
        break;
    }
}

✅ Efficient early termination with break
✅ Reuses battle-tested collect_interfaces helper
✅ Proper error handling with let Some

TypeScript Implementation (groovy.ts:157-163):

for (let i = 0; i < node.childCount; i++) {
    const child = node.child(i);
    if (child && child.type === 'extends_interfaces') {
        collectGroovyParentInterfaces(child, ifaceName, node.startPosition.row + 1, ctx);
        break;
    }
}

✅ Mirrors Rust pattern exactly
✅ New collectGroovyParentInterfaces helper follows same recursion logic
✅ Proper null checks and early termination

Minor Observations

  1. Architecture Note: The PR correctly notes that HIERARCHY_SOURCE_KINDS doesn't include 'interface', so emitted relations won't materialize as graph edges until that filter is updated. This is appropriately framed as a separate broader change.

  2. Test Quality: Both test suites use identical test cases (interface Serializable extends Comparable, Cloneable) ensuring engine behavior consistency.

  3. Documentation: Excellent inline comments explaining tree-sitter grammar specifics and why the approach works.

Critical Concerns

None found. This is a well-executed bug fix with proper testing and engine parity.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Clean, well-tested fix that properly addresses interface inheritance extraction. The implementation correctly handles tree-sitter grammar specifics, maintains engine parity, and includes comprehensive test coverage. The only minor issue is the broader architectural question about HIERARCHY_SOURCE_KINDS, which is correctly identified as a separate concern.

Excellent work - this demonstrates deep understanding of both the tree-sitter grammar and the codegraph extraction pipeline.


@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 18, 2026

Greptile Summary

This PR fixes a missing-edge bug in both the Rust and TypeScript Groovy extractors: interface X extends Y, Z declarations were previously emitting only a Definition but no ClassRelation entries because neither engine walked the unnamed extends_interfaces child. The Rust fix delegates to the existing collect_interfaces helper; the TS fix adds a symmetric collectGroovyParentInterfaces that mirrors the same type_list recursion.

  • Rust (groovy.rs): handle_interface_decl now scans for the extends_interfaces child and calls collect_interfaces, reusing the helper already used for class implements clauses.
  • TypeScript (groovy.ts): handleGroovyInterfaceDecl gains the same scan plus a new collectGroovyParentInterfaces helper that handles type_identifier, identifier, scoped_type_identifier, generic_type, and recursive type_list — matching the Rust helper's arms.
  • Tests: Both engines gain a test for interface Serializable extends Comparable, Cloneable {} verifying both parent-interface relations are emitted.

Confidence Score: 4/5

Safe to merge; the change is well-scoped and the emitted relations are currently gated by a downstream filter that does not yet include interface, so there is no production impact from the minor TS/Rust line-number inconsistency.

Both engines correctly identify parent interfaces and emit ClassRelation entries. The only divergence is that the TS helper always forwards the interface declaration's start line through recursive type_list calls, while the Rust helper re-evaluates the current node's line at each recursion level.

src/extractors/groovy.ts — the collectGroovyParentInterfaces line-forwarding behavior should be aligned with the Rust engine before interface is added to HIERARCHY_SOURCE_KINDS.

Important Files Changed

Filename Overview
crates/codegraph-core/src/extractors/groovy.rs Adds extends_interfaces child traversal to handle_interface_decl, reusing the existing collect_interfaces helper for type_list recursion. Logic is correct and consistent with class-level interface handling.
src/extractors/groovy.ts Adds equivalent collectGroovyParentInterfaces helper for the TS engine. Minor inconsistency: always uses the interface declaration's start line for emitted relations, while the Rust engine uses the extends_interfaces (or type_list) node's own start line.
tests/parsers/groovy.test.ts Adds a new test for interface X extends Y, Z covering both parent interfaces. Mirrors the Rust test structure; does not cover generic or scoped parent interface names.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[interface_declaration node] --> B{Find extends_interfaces child}
    B -- found --> C[collect_interfaces / collectGroovyParentInterfaces]
    B -- not found --> D[No ClassRelation emitted]
    C --> E{Child node type}
    E -- type_identifier / identifier / scoped_type_identifier --> F[emit ClassRelation]
    E -- generic_type --> G[extract first child text, emit ClassRelation]
    E -- type_list --> H[recurse into type_list]
    H --> E
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(groovy): emit ClassRelation for inte..." | Re-trigger Greptile

Comment thread src/extractors/groovy.ts
Comment on lines +166 to +191
function collectGroovyParentInterfaces(
parent: TreeSitterNode,
name: string,
line: number,
ctx: ExtractorOutput,
): void {
for (let i = 0; i < parent.childCount; i++) {
const child = parent.child(i);
if (!child) continue;
switch (child.type) {
case 'type_identifier':
case 'identifier':
case 'scoped_type_identifier': {
ctx.classes.push({ name, implements: child.text, line });
break;
}
case 'generic_type': {
const inner = child.child(0)?.text;
if (inner) ctx.classes.push({ name, implements: inner, line });
break;
}
case 'type_list':
collectGroovyParentInterfaces(child, name, line, ctx);
break;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Line number diverges from Rust engine on multi-line declarations

collectGroovyParentInterfaces receives line once (the interface declaration's startPosition.row + 1) and forwards the same value through every recursive call into type_list children. The Rust collect_interfaces instead evaluates start_line(interfaces) at each recursive invocation, so it tracks the actual start line of whatever node (extends_interfacestype_list) is being processed at that moment.

For a multi-line interface — e.g. interface X\n extends Y, Z {} — the Rust engine would emit line 2 for the relation, while the TS engine always emits line 1. This is currently benign (the downstream HIERARCHY_SOURCE_KINDS filter doesn't include interface), but it means the two engines will produce divergent ClassRelation.line values once that filter is extended.

Fix in Claude Code

@github-actions
Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

4 functions changed4 callers affected across 2 files

  • handle_interface_decl in crates/codegraph-core/src/extractors/groovy.rs:159 (1 transitive callers)
  • extracts_interface_inheritance in crates/codegraph-core/src/extractors/groovy.rs:541 (0 transitive callers)
  • handleGroovyInterfaceDecl in src/extractors/groovy.ts:141 (2 transitive callers)
  • collectGroovyParentInterfaces in src/extractors/groovy.ts:166 (3 transitive callers)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

follow-up: emit ClassRelation for Groovy interface inheritance (both engines)

1 participant