Noir::TreeSitterKotlinKtorRouteExtractor
Tree-sitter-backed Ktor DSL route extractor.
Walks the canonical Ktor server idiom:
routing {
get("/x") { ... }
route("/api") {
post("/items") { val item = call.receive < Item > () }
}
authenticate("auth-jwt") {
get("/profile") { ... }
}
}
Recognises:
- Verb DSL calls —
get/post/put/delete/patch/head/options/querywith a string-literal path argument and a trailing lambda body. route("/x") { ... }blocks contributing to the path prefix.authenticate("realm") { ... }blocks acting as transparent wrappers (no prefix change). Tagging is handled elsewhere; we just descend so wrapped routes are still discovered.routing { ... }andapplication.routing { ... }entry points.- Type-safe
@Resourcerouting —get<VideoStream> { },resource<Login> { post { } },method(HttpMethod.Get) { handle<T> }— with cross-file parent-path composition. - Inside each verb's lambda body:
call.receive<T>()→bodyparameter typedTasjsoncall.parameters["name"]→nameparameter asquerycall.request.headers["name"]→nameparameter asheader
Not covered yet:
install(plugin) { ... }plugin scoping that affects routing.
Constants
API-documentation DSL blocks. They hang off a route
(get("/x") { }.describe { ... }) or nest inside one, and they
describe an endpoint rather than declaring one — no route is ever
registered inside them.
They have to be skipped because the parameter sub-DSL names a query
parameter with query("name") { ... }, which is exactly the shape of
the QUERY verb call (RFC 10008): a name, a trailing lambda, inside the
routing scope. Without this, every documented query parameter became a
QUERY route named after the parameter — query("chunk_size") in Ktor's
own httpbin sample surfaced as QUERY /chunk_size.
Only the lambda is skipped, never the receiver: the block is chained onto the very route it documents, so dropping the whole call would take the real route with it.
Pass-through DSL calls — descend into their lambda body without
changing the path prefix. routing is the entry point;
authenticate wraps a sub-tree behind an auth realm; the
remaining names cover the common Ktor scoping helpers. install
is handled separately (see walk) because only install(Routing)
contributes routes — every other plugin config block must NOT be
walked as routing.
Instance methods
Resolve each raw resource to its full URL path. The parent is the
first primary-constructor property whose type is itself a resource
(root: Root → /api); the child path joins onto it. Returns a
map keyed by BOTH the dotted lexical name (Articles.New) and the
bare simple name (TagsResource) so get<...> references resolve
either way.
Collect every @Resource("path")-annotated class/object in a file
(including nested ones, with their dotted lexical name). The
analyzer gathers these across the whole project before composing
full paths, since a resource's parent is often declared in another
module (Ktor's KMP commonMain resource definitions).