Skip to content

fix(extract): Java extends/implements + resolve cross-file imports#482

Closed
Goodday12 wants to merge 1 commit into
Graphify-Labs:v4from
Goodday12:fix/java-extends-implements-imports
Closed

fix(extract): Java extends/implements + resolve cross-file imports#482
Goodday12 wants to merge 1 commit into
Graphify-Labs:v4from
Goodday12:fix/java-extends-implements-imports

Conversation

@Goodday12

@Goodday12 Goodday12 commented Apr 21, 2026

Copy link
Copy Markdown

Closes #435 (Java inheritance support). Also fixes a related Java-imports bug that would have blocked #435 from actually working end-to-end.

Summary

Two issues in Java AST extraction that silently drop all Java-type edges from the graph. On any real Java corpus, the structural_type layer (imports / extends / implements) comes out empty.

Issue 1 — Java inheritance was never extracted

The class handler inside _extract_generic has language-specific blocks for Python (inherits), Swift (inherits), and C# (inherits) — but nothing for Java, even though tree-sitter-java exposes the superclass / interfaces / extends_interfaces fields.

Result: for a Java corpus of 5,727 files, extends = 0 and implements = 0.

Issue 2 — Java import edges had dangling targets

_import_java emits edges whose target is just the bare class name (_make_id(module_name) → e.g. list for java.util.List), while real node ids use {stem}_{classname}. Those edges never match a node id, so build_from_json silently drops every single one at line 75-76 of build.py.

Result: imports = 0 in the built graph, even though _import_java ran.

What this PR changes

graphify/extract.py

  • New tree_sitter_java block in the class handler (alongside the existing Python / Swift / C# blocks). Emits:

    • extends edge for class X extends Y (via node.child_by_field_name(\"superclass\"))
    • implements edges for class X implements A, B (via node.child_by_field_name(\"interfaces\") → super_interfaces → type_list)
    • extends edges for interface X extends A, B (via the extends_interfaces child)
    • When the parent/interface is defined outside the current file, a synthetic external node is added (same pattern as the existing Python / Swift / C# inheritance blocks).
  • New _resolve_cross_file_java_imports helper mirroring the existing _resolve_cross_file_imports (Python). Runs a second pass on Java corpora, builds a {ClassName: [node_id, …]} index across all nodes, re-parses each file's imports, and emits resolved imports edges pointing at real class nodes elsewhere in the corpus.

    • Wildcard imports (a.b.*) are skipped — the target is ambiguous.
    • static imports strip the static keyword and resolve the class name (not the method).
    • Stdlib / third-party names left unresolved produce no edge (they would be dropped anyway).
  • extract() now calls the Java resolver after the existing Python one, in the same try/except style so an import failure in tree_sitter_java only logs a warning.

Tests

All four new tests pass locally; the full upstream suite (437 tests) passes with no regressions.

  • tests/test_languages.py::test_java_finds_extends
  • tests/test_languages.py::test_java_finds_implements
  • tests/test_languages.py::test_java_finds_interface_extends
  • tests/test_multilang.py::test_extract_resolves_java_cross_file_imports

Two new fixtures: sample_inherit.java (single inheritance + multi-interface + interface-extends-interface) and sample_imported.java (defines a class imported cross-file).

Validated on a real Java corpus

Spring / JPA / Lombok monorepo of 5,727 .java files parsed with no errors:

relation before this PR after this PR
extends 0 2,072
implements 0 1,285
imports (internal) 0 58,019

Before the fix, the entire structural_type layer of this corpus had 11 edges (all from README.md via the LLM pass, none from AST). After the fix it has 61,376 real AST edges.

Test plan

  • All 4 new tests pass against the patched graphify/extract.py
  • Full upstream pytest tests/ — 437 passed, 0 failed
  • Validated on 5,727-file Java codebase: counts above
  • Cross-language regression check: no changes to non-Java code paths

Notes

  • _import_java is left in place. The edges it produces still reach build_from_json and still get dropped for stdlib / third-party imports — that is the expected behaviour for dangling externals. The new cross-file pass now re-resolves the internal ones correctly.
  • extends / implements are used as distinct relation names (rather than the inherits used by Python / Swift / C#) because Java separates the two cleanly and the distinction is useful downstream. If you'd prefer to unify under inherits, happy to adjust.

Two issues with Java extraction that silently dropped all Java-type edges:

1. Inheritance relations (extends / implements) were never extracted. The
   class handler in `_extract_generic` had language-specific blocks for
   Python (inherits), Swift (inherits), and C# (inherits) but nothing
   for Java, even though tree-sitter-java exposes the `superclass`,
   `interfaces`, and `extends_interfaces` fields.

2. Java imports had dangling targets. `_import_java` emitted edges whose
   target was just the class name (`_make_id(module_name)` e.g. `list`),
   while real node ids use `{stem}_{classname}`. Those edges failed to
   match any node in `build_from_json` and were silently discarded,
   leaving large Java corpora with effectively zero type-level edges.

## Changes

- `extract.py`: new `tree_sitter_java` block in the class handler emits
  `extends` for `class X extends Y`, `implements` for
  `class X implements A, B`, and `extends` for interface-to-interface
  inheritance (`interface X extends A, B`). When the parent is defined
  outside the current file, a synthetic external node is added (same
  pattern as the existing Python, Swift, and C# blocks).

- `extract.py`: new `_resolve_cross_file_java_imports` helper runs a
  second pass on Java corpora and rewrites file-level imports into
  resolved edges pointing at real class nodes elsewhere in the corpus.
  Wildcard imports (`a.b.*`) are skipped; stdlib / third-party names
  left unresolved produce no edge (they would be dropped anyway).

- `extract()`: calls the Java resolver after the existing Python one.

## Tests

- `test_languages.py::test_java_finds_extends`
- `test_languages.py::test_java_finds_implements`
- `test_languages.py::test_java_finds_interface_extends`
- `test_multilang.py::test_extract_resolves_java_cross_file_imports`

New fixtures `sample_inherit.java` and `sample_imported.java` exercise
single-inheritance, multiple-interface implementation, interface-extends-
interface, and cross-file import resolution.

## Verified on a real Java corpus

On a private Spring / JPA / Lombok codebase of 5,727 .java files:

  before this patch: `extends` = 0, `implements` = 0, `imports` = 0
                     (all dropped silently at build time)

  after this patch:  `extends` = 2,072, `implements` = 1,285,
                     `imports` = 58,019 resolved internally

Full upstream test suite (437 tests) passes with no regressions.
@safishamsi

Copy link
Copy Markdown
Collaborator

Implemented in v0.4.31 — Java extends/implements/interface-extends extraction added to the structural walker, plus _resolve_cross_file_java_imports() two-pass resolver. Both wired into extract() with exception guards.

@safishamsi safishamsi closed this Apr 22, 2026
safishamsi added a commit that referenced this pull request Apr 23, 2026
…nheritance, aggregated HTML viz, check-update subcommand
matzls pushed a commit to matzls/graphify that referenced this pull request May 10, 2026
…ify-Labs#490: legacy schema canonicalization, Java inheritance, aggregated HTML viz, check-update subcommand
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.

Feature Request: Add Java Inheritance Relationship Support in Tree-sitter Parsing

5 participants