Skip to content

feat(scanner): add a Kotlin scanner - #124

Open
matteocodogno wants to merge 12 commits into
MrLesk:mainfrom
matteocodogno:task-576-kotlin-scanner
Open

matteocodogno wants to merge 12 commits into
MrLesk:mainfrom
matteocodogno:task-576-kotlin-scanner

Conversation

@matteocodogno

Copy link
Copy Markdown

Backlog task: TASK-576 - Add an experimental Kotlin scanner (backlog/tasks/task-576 - Add-an-experimental-Kotlin-scanner.md).

What this adds

  • Actor: a developer scanning a repository that contains Kotlin.
  • Entry point: groma scanner add @groma/scanner-kotlin, then groma scan.
  • Result: Kotlin files appear on the map with the same evidence the experimental Scala scanner gives: source inventory, top-level symbols, operations with UTF-16 positions, unresolved call sites, and Code outlines with Kotlin visibility.

The package (plugins/scanners/kotlin) follows the Scala scanner's delivery: a TypeScript entry and adapter run a bundled worker on a bundled Java runtime. The worker is written in Kotlin and parses with kotlin-compiler-embeddable 2.4.21 (parse only: no analysis, classpath, Gradle or Maven). Scanning needs no installed JDK, Kotlin compiler, build or network. A parse or read failure rejects the whole observation and names the file.

It is registered in the official catalog and the scanner release assembly beside Scala, and documented in docs/scanners/kotlin/index.md. No new scan flow is introduced, so there is no new Gherkin scenario; the approved example is the fixture test/fixtures/kotlin-source.

Out of scope: HTTP facts, Gradle/Maven module discovery, Kotlin scripts (.kts), body fingerprints.

Kotlin-specific rules worth a look

  • Companion-object functions belong to the enclosing type (Orders.create).
  • Constructors are named constructor; a primary constructor is an outline member but not an operation.
  • Only call expressions are invocations; operators and infix calls are not.
  • Default exclusions are build/, .gradle/, **/src/test/ and **/src/*Test/ (the **/ prefix is needed because an inner-slash pattern is otherwise anchored at the repository root, which would scan tests in every Gradle module).
  • CRLF files: the compiler parser rejects carriage returns, so the worker replaces each with a space before parsing; positions still refer to the file on disk.

Checks

Run on macOS arm64 with Bun 1.4.2 and JDK 25:

  • test-bun/kotlin-scanner.test.ts (builds an isolated package): passes.
  • test-bun/scanner-fresh-checkout.test.ts for kotlin against the built package (empty home, only git on PATH): passes.
  • test-bun/scanner-release.test.ts: passes.
  • bun run check: lint (existing findings only) and typecheck pass; Node 16/16; Bun 771 pass, 52 skip, 6 fail. All six are Swift scanner tests failing at swiftc with no such module 'SwiftParser' on my machine (Command Line Tools without swift-syntax). None involves Kotlin; CI should confirm.

Not run: Linux and Windows hosts, and the multi-host release assembly with real artifacts.

Known limitations and decisions for the maintainer

  • Package size. The compiler's parser links javax.swing.Icon, so the jlinked runtime needs java.desktop: about 78 MB per host (about 24 MB compressed), plus 59 MB of compiler jars shared by all hosts. Estimated about 175 MB compressed for five hosts, below the 214.6 MB C# archive that was rejected but not by much. A stub javax.swing.Icon on a java.base-sized runtime also parsed correctly in a spike (about 120 MB total) but was not adopted; say if you prefer it or a per-host split.
  • Compiler API. The worker uses KotlinCoreEnvironment, which requires opt-ins and is marked by JetBrains as planned for rework, so a compiler upgrade may need worker changes.
  • Visibility is syntactic. An override without a modifier reads as public even when it inherits protected.
  • Third-party notices copy Kotlin's NOTICE, the Apache 2.0 text, the upstream third-party list and all 30 license texts it names from the v2.4.21 tag.
  • The architecture map gains one component, kotlin-src-index (Kotlin source scanner), in the CLI container's Language scanners group.

This is a fork PR, so the Groma architecture comparison needs a manual run.

🤖 Generated with Claude Code

matteocodogno and others added 2 commits October 9, 2026 17:46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
matteocodogno and others added 10 commits October 9, 2026 18:20
The parser rejected a file starting with a UTF-8 byte order mark, which failed the whole scan. The mark is replaced by a space, as carriage returns already are, so offsets and lines still refer to the file on disk.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The outline kept the backticks of a quoted identifier while scan symbols and operations report the unquoted name, so a Code link written with the symbol name never marked its outline entry. The outline now reports the same name as scans.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… too deep

A long generated chain overflowed the stack in the recursive call walk and the scan died with a raw JVM trace naming no file. The adapter now starts the worker with a 512 MB stack, and a file nested deeper than that fails the scan by name like any other unreadable file.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The default exclusion build/ also dropped real source in a package directory named build, such as com.android.build. The defaults now restore a build folder under a source set's kotlin/ or java/ root, where it is a package and never Gradle output.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The default src/*Test/ missed src/testDebug/, src/androidTestDebug/ and src/testFixtures/, so their test code was scanned as product code although the documentation says test source sets are excluded. The defaults now also name a test source set followed by a capitalised variant.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One observation covers every selected file at about 115 bytes per source line, so a repository of roughly a million lines exceeded the 64 MB buffer and the scan was rejected. The adapter now reads the worker's output without a fixed limit.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A secondary constructor searched its whole declaration for calls while a function searched only its body, so a call in a parameter default was evidence for one and not the other. A function now searches its whole declaration too, because it runs its defaults.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutable var with a lambda initializer was reported as a function symbol and operation, which showed reassignable state as a fixed function. Only a val is a function value now.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The build shipped whatever Maven Central returned for the pinned versions, so a truncated or altered jar would still be published. Each artifact now carries the SHA-256 of the reviewed jar, matched against Maven Central's published SHA-1 for the same bytes, and a download that differs fails the build.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Kotlin adapter and build were near copies of the Scala scanner's, and the notices writer existed in three build scripts. plugins/scanners/jvm-worker.ts now owns running a worker on the bundled Java runtime and mapping its outline to Code entries for Kotlin and Scala. plugins/scanners/jvm-package.ts owns package assembly for both, and the notices writer and JDK tool lookup the Java build also uses.

The shared runner has no fixed output limit, so the Scala adapter loses its 64 MB limit too. The worker module joins the Scala scanner component; the build helper is excluded from the architecture scan like the other build scripts.

Refs: TASK-576
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@matteocodogno
matteocodogno force-pushed the task-576-kotlin-scanner branch from f6a58dd to d4d7faf Compare October 9, 2026 16:40
@matteocodogno matteocodogno changed the title TASK-576 - Add an experimental Kotlin scanner feat(scanner): add a Kotlin scanner Oct 9, 2026
@evertdhont

Copy link
Copy Markdown

Hey @MrLesk @matteocodogno, sorry to post an unrelated question but it's the only way I found to contact you, I would love to raise an issue but I see it's turned off for this repo, is this intentional? the README showed links to the /issues page but when landing there I see "Issue creation is restricted in this repository" below the "Issues" button...

image

@MrLesk

MrLesk commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Hey @MrLesk @matteocodogno, sorry to post an unrelated question but it's the only way I found to contact you, I would love to raise an issue but I see it's turned off for this repo, is this intentional? the README showed links to the /issues page but when landing there I see "Issue creation is restricted in this repository" below the "Issues" button...

image

Sorry about that @evertdhont
I just enabled the issues section

This branch was successfully deployed

1 active deployment
github-pages — d4d7faf9 Deployed Oct 9, 2026 by matteocodogno via deploy #192
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.

3 participants