Noir::TreeSitterKotlinRouteExtractor
Tree-sitter-backed Kotlin route extractor.
Spring-style annotation routing — class-level @RequestMapping-family
annotations composing with method-level mapping annotations. Mirrors
TreeSitterJavaRouteExtractor but for Kotlin's distinct AST shape
(annotations live in modifiers, functions use
function_declaration, primary constructors carry DTO fields, etc.).
This file holds the class-tree walking, mapping prefixes and controller/interface/Feign routes; the sibling files in this directory reopen the module with one concern each (STOMP, GraphQL, gateway, WebFlux functional DSL, shared decoding helpers).
Ktor's DSL routing { get("/x") { ... } } is a different authoring
style and lives in a separate walker
(kotlin_ktor_route_extractor_ts.cr).
Constants
Spring mapping annotation names → HTTP verb. nil means look at
the annotation's method = argument. Same table as the Java
extractor for consistency.
const val X = A + "/b" + Paths.C: string literals and (dotted)
constant names joined by +. Only const — annotation arguments
must be compile-time constants, so a plain val concatenation can
never reach a mapping path. The literal-only regex below would
otherwise keep just the first literal of such a line.
Kotlin scalars a resource method binds from a single request value,
lower-cased like the Java extractors' PRIMITIVE_TYPES.
class/object/interface after any modifiers (internal object,
data object, enum class). A companion object is not a type of
its own: its constants are read through the enclosing class.
Annotation values never legitimately nest more than a few levels
([arrayOf("/a")]). The shared MAX_AST_DEPTH backstop is too
loose for the two value walkers below: their frames are ~10KB in a
debug build, so ~900 nested ((...)) / [[...]] already overran
the fiber stack and aborted the whole scan.
ponytail: nested multi-path mappings multiply, so the product is cut at MAX_CLASS_PREFIXES; raise it if a real app ever needs more.
A resolved route constant is a URL path; anything longer is a
template chain doubling itself (B = "$A$A", C = "$B$B", ...),
which used to grow until the analyzer failed. Such a value is left
unexpanded.
Flow<Book> is the coroutine spelling of Publisher<Book>.
Calls other than path(...) inside a nest predicate — accept(JSON),
queryParam("k", "v") — never carry the prefix.
Instance methods
Whether decl carries @<name> (simple name) in its own modifiers.
Resolve Kotlin string-template interpolations inside collected
constant values, e.g. const val STATIC_URL = "$PUBLIC_URL/static"
with PUBLIC_URL = "/public" becomes /public/static. The regex
capture in extract_string_constants stores the raw $PUBLIC_URL
text, so without this a path built from such a constant keeps the
literal $VAR (or, as an inline annotation literal, mis-parses it
as a {VAR} path placeholder). Unresolved references (e.g. Spring
${config.property} placeholders) are left untouched. Bounded
iterations resolve transitive chains. Names missing from
constants are looked up in fallback (another file's constants).
{start_byte, end_byte, callees} for every function in source, so
an adapter that finds a route by annotation offset (Quarkus
@Route) can pick the enclosing function's callees.
@ApplicationPath("/api") on a Kotlin Application subclass.
_from(root, source) — accept a pre-parsed root so the Kotlin
Spring analyzer can amortise the parse across multiple
extractions on the same file. Tree lifetime is the caller's
responsibility.
Nested types
- Noir::TreeSitterKotlinRouteExtractor::ArmeriaRoute
- Noir::TreeSitterKotlinRouteExtractor::ControllerInterfaceImplementation
- Noir::TreeSitterKotlinRouteExtractor::GraphqlRoute
- Noir::TreeSitterKotlinRouteExtractor::KotlinDtoIndex
- Noir::TreeSitterKotlinRouteExtractor::ResourceAnnotation
- Noir::TreeSitterKotlinRouteExtractor::Route