module

Noir::TreeSitterAdonisJsExtractor

Tree-sitter-backed AdonisJS extractor.

AdonisJS exposes a Laravel-flavoured router via either Route (v5, @ioc:Adonis/Core/Route) or router (v6, @adonisjs/core/services/router). Both share the same shape:

Route.get('/users', 'UsersController.index')

Route.group(() => {
    Route.get('/posts', 'PostsController.index')
    Route.post('/posts', 'PostsController.store')
}).prefix('/api/v1').middleware('auth')

Route.resource('articles', 'ArticlesController').apiOnly()

The fluent modifiers (.prefix, .middleware, .as, .domain, .namespace, .where, .apiOnly, .only, .except) live on the result of the registration call. We walk the chain outermost-first so the modifiers can influence the prefix / resource action set before the inner registration emits.

Recognised:

  • Verb methods: .get, .post, .put, .delete, .patch, .options, .head, plus .any (fans out to GET / POST / PUT / DELETE / PATCH).
  • .group(callback) — walks the callback with the current accumulated prefix; subsequent .prefix(...) modifiers on the group result are folded in.
  • .resource(name, controller) — emits the seven resourceful routes (index / create / store / show / edit / update as PUT and PATCH / destroy); .apiOnly() drops the create and edit form routes, .only([...]) restricts to the listed actions and .except drops the listed ones. A dotted name nests: posts.comments is /posts/:post_id/comments.

Out of scope for this first cut:

  • Per-handler request-helper scanning. AdonisJS handlers receive ({ request, response, params }) — the dominant pattern is to point at a 'Controller.method' string, which would need cross-file resolution to scan.
  • .where('id', /\d+/) constraint annotations beyond stripping the modifier.
  • Domain / subdomain routing.

Constants

ANY_VERBS = ["GET", "POST", "PUT", "DELETE", "PATCH"]
FORM_ACTIONS = ["create", "edit"]

The HTML form routes .apiOnly() removes.

GROUP_METHOD = "group"
HTTP_VERB_METHODS = {"get" => "GET", "post" => "POST", "put" => "PUT", "delete" => "DELETE", "patch" => "PATCH", "options" => "OPTIONS", "head" => "HEAD"}
METHOD_DECL_RE = /^[ \t]*(?:(?:public|private|protected|static|async|override|readonly|abstract|declare)\s+)*([A-Za-z_$][\w$]*)\s*(?:<[^\n{}]*>\s*)?\(/m

async store({ request }: HttpContext): Promise<{ id: number }> {. The { of Promise<{...}> is not the body. A bare store(data) call is not a method either: it has no body brace after the params.

METHOD_KEYWORDS = Set {"if", "for", "while", "switch", "catch", "function", "return", "with", "do", "else", "try", "new", "await", "import", "class"}

Statements that share a method's name( shape. A call to one of these is not a controller action.

RESOURCE_ACTION_NAMES = RESOURCE_ACTIONS.map(&.[](0)).uniq!
RESOURCE_ACTIONS = [{"index", "GET", ""}, {"create", "GET", "/create"}, {"store", "POST", ""}, {"show", "GET", "/:id"}, {"edit", "GET", "/:id/edit"}, {"update", "PUT", "/:id"}, {"update", "PATCH", "/:id"}, {"destroy", "DELETE", "/:id"}]

{action, verb, suffix} in AdonisJS's own registration order.

RESOURCE_FILTERS = Set {"only", "except", "apiOnly"}

Chain links that narrow a resource's action set.

ROUTER_RECEIVERS = Set {"router", "Route"}

The only identifiers AdonisJS registers routes on: Route (v5, @ioc:Adonis/Core/Route) and router (v6, @adonisjs/core/services/router). Gating the verb/group/resource dispatch on the call chain's ROOT receiver being one of these stops every other .get/.delete/... call shaped like a route from becoming a phantom endpoint — env.get('STRIPE_SECRET_KEY'), session.get('plan'), a Lucid Model.query()...delete('*'), etc.

STRING_LITERAL_TYPES = Set {"string", "template_string"}

Route paths and controller references are written as either a quoted string or a backtick template literal — tree-sitter-javascript emits template_string for the latter. decode_string has always known how to unwrap both; only the call-site gates were rejecting backticks.

TRANSPARENT_MODIFIERS = Set {"middleware", "as", "domain", "where", "namespace"}

Modifiers that don't change the path or verb — we just walk the receiver with the same prefix.

Instance methods

extract_routes(source : String, include_callees : Bool = false, path : String = "") : Array(Route)
Source
method_body(source : String, action : String) : String | Nil

The {...} body of action in a controller module, or nil when the name is only a call. source is comment-stripped.

Source

Nested types