fix: correct JVM dependency graphs, Kotlin import aliases, and container entry points - #736
Closed
apandeya wants to merge 3 commits into
Closed
fix: correct JVM dependency graphs, Kotlin import aliases, and container entry points#736apandeya wants to merge 3 commits into
apandeya wants to merge 3 commits into
Conversation
Two defects produced empty or incomplete dependency graphs for tree-sitter-backed JVM languages (Kotlin in particular): 1. ts_build_dep_graph compared resolver output (absolute paths) against the raw file list, which finders may return as relative paths. No edge ever matched, so every file reported zero importers and the orphaned and test-coverage detectors mass-flagged hundreds of heavily-used files as dead code. 2. Same-package references need no import statement in JVM languages, so even a correct import graph under-counts importers: DTOs, mappers and helpers referenced by same-package files looked orphaned. A new optional spec pair (package_query + top_level_name_query) registers same-package references as importers of the declaring file only, so cycle detection stays import-based. Wired up for Kotlin, including body-less declarations (data classes, interfaces, type aliases). Also excludes Gradle build scripts (build.gradle.kts / settings.gradle.kts) from Kotlin orphan findings via entry_patterns. Verified against a real Spring Boot Kotlin codebase: orphaned findings dropped from 280 (virtually all false positives) to 5 genuinely unreferenced files, and test-coverage findings now reflect real importer counts.
Kotlin's tree-sitter grammar nests aliased imports as import_header > import_alias > [as, type_identifier], but _extract_alias never descended into import_alias and did not accept type_identifier leaves. Aliased imports were therefore checked under their original name, which never appears in the file, and reported as unused — a false positive for every aliased import, a pattern Kotlin style guides actively encourage for disambiguation.
Files wired by dependency-injection frameworks have zero static importers by design. Mirroring the existing Next.js convention awareness, JVM files (.kt/.kts/.java/.scala) are no longer flagged as orphaned when they carry Spring-style stereotypes (@service, @RestController, @configuration, listeners, scheduled jobs, ...), implement Spring Data repository interfaces (proxied at runtime), or declare a main entry point (fun main / public static void main).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Scanning a real Spring Boot Kotlin codebase (446 files, hexagonal architecture) produced 627 findings, of which ~567 were false positives. Three root causes, all in shared tree-sitter/JVM machinery:
1. Zero-edge dependency graphs (critical)
ts_build_dep_graphcompared resolver output (absolute paths) against the raw file list, whichfind_source_filesmay return as relative paths. Theresolved not in file_setcheck never matched, so no import edge was ever recorded and every file reportedimporter_count = 0. Theorphaneddetector then flagged 280 heavily-used files ("zero importers, not an entry point") — including a domain model with 20 importers — andtest_coverageflagged the same 280 files as untested modules.2. Kotlin import aliases invisible to unused-import analysis
The Kotlin grammar nests aliases as
import_header > import_alias > [as, type_identifier], but_extract_aliasnever descended intoimport_aliasand didn't accepttype_identifierleaves. Everyimport com.foo.Bar as Bazwas checked under the nameBar, which never appears in the file — flagging every aliased import as unused. (Aliased imports are a recommended Kotlin convention for disambiguation.)3. DI-wired and same-package files look orphaned
Even with a correct graph, Spring beans (
@Service,@RestController, ...) are container-instantiated and have zero static importers by design, and same-package references in JVM languages need no import statement — so DTOs, mappers, and helpers referenced only by same-package files still looked orphaned.Fix
imports/graph.py: map normalized absolute paths back to the file-list key form (handling both cwd-relative and scan-root-relative entries) before recording edges; add optional same-package reference detection via new spec fields.treesitter/types.py/specs/compiled.py: new optionalpackage_query+top_level_name_queryspec fields, wired up for Kotlin. Same-package references are registered only as importers of the declaring file (never as outgoing imports), so cycle detection stays import-based. The name query deliberately doesn't require a body, sodata classes, interfaces and type aliases are covered.analysis/unused_imports.py: descend intoimport_aliasnodes and accepttype_identifier/simple_identifierleaves in_extract_alias.engine/detectors/orphaned.py: mirroring the existing Next.js convention awareness, JVM files (.kt/.kts/.java/.scala) carrying Spring stereotypes, Spring Data repository interfaces, or amainentry point are treated as entry points.languages/kotlin/__init__.py: exclude Gradle build scripts (build.gradle.kts,settings.gradle.kts) from orphan findings.Verification
Against the codebase that surfaced this (Spring Boot + Kotlin, hexagonal architecture):
orphanedunused(imports)test_coverageTests: new regression tests for relative-path file lists, Kotlin alias extraction, same-package references (real grammar), and JVM entry-point markers. Full suite: 5818 passed; the only failures (
test_transitive_modules,test_cli::TestStatePath,test_bash_unused_imports) reproduce on cleanmainin this environment and are unrelated.