class

Noir::GoNamedHandler

Inherits Reference < Object

Named-handler attribution for the Go adapters' line loops.

Those loops credit every accessor line (c.Query("x")) to the most recent route above it. That is right for inline closures, but a named handler's body (r.GET("/a", listA) ... func listA(c) {...}) sits below the LAST route of the file, so all of its accessors landed on that last route and the route that actually names the handler got nothing.

Usage: build one per file from the extracted routes, claim? each line first (skip the legacy attribution when it returns true), bind every endpoint with its route, then each_attribution once the loop is done (the handler may be declared above or below its route).

Every plain handler argument of a route is resolved — middleware too (r.GET("/x", Auth, listX) binds both bodies), and a middleware factory called with no arguments (RateLimit()) resolves to the factory body. Only unambiguous references are claimed; everything else keeps the legacy attribution:

  • a bare listA resolves to the one func listA in this file;
  • a selector h.List resolves to the one method List in this file whose receiver type is the type h is declared with here, or, when h's type is not visible in this file, whose receiver is named h. A receiver that is an imported package (handlers.List) never resolves to a local method;
  • given a Scope, a name not declared in this file is looked up in the other files of its package (same rules), and pkg.Fn in the scanned package pkg imports. Those bodies are attributed from their own source, since the line loop never walks them;
  • a function that registers routes itself is never claimed.

A wrapped handler (guard(func(c) { return listA(c) })) binds every function its text names (guard, listA), and a bound body's own calls bind one hop further (listA calling parseFilter(c)). Any other body in the file that registers no route is claimed and credited to nobody: a helper below the last route is not part of that route.

Constants

ASSIGNED_TYPE_RE = /(?:\A|[\n;{(])[ \t]*(?:var[ \t]+)?([A-Za-z_]\w*)[ \t]*:?=[ \t]*(?:&[ \t]*)?(?:([A-Za-z_]\w*)\.)?([A-Za-z_]\w*)[ \t]*\{/

Declarations that pin a name's type, scanned once per file (see declared_types). Assignments are anchored at a statement start, so the second name of a, b := &X{}, newY() is never typed X. Group 2 is the type's package qualifier, if any.

CALL_RE = /(?<![\w.])([A-Za-z_]\w*)\s*\(/
CLOSURE_ARG = /\bfunc\s*\(/
CLOSURE_HANDLER = /\A\s*func\s*\(/
DECLARED_TYPE_RE = /(?:\A|[\n(,]|\bvar)[ \t]*([A-Za-z_]\w*)[ \t]+\*?(?:([A-Za-z_]\w*)\.)?([A-Za-z_]\w*)/

name *T as a parameter, receiver, struct field or var.

HANDLER_RESULT = /\A\s*(?:error\s*)?\{/

The text after a definition's parameter list when it is a handler: no result (func(c *gin.Context), func(w, r)) or an error (func(c echo.Context) error). A wrapper or factory returns the next handler (echo.HandlerFunc, func(...)), so it never matches.

LIBRARY_READS = { {/\Agin\.BasicAuth(?:ForRealm)?\s*\(/, "c.Request.Header.Get(\"Authorization\")"} }

Library middleware whose request read lives in a dependency the scan never sees, with that read as the source line the adapters parse: gin's BasicAuth checks the Authorization header.

NAME_RE = /(?<![\w.])[A-Za-z_]\w*/

An unqualified name in a wrapped handler's text, and an unqualified call in a bound body.

NEW_TYPE_RE = /(?:\A|[\n;{(])[ \t]*(?:var[ \t]+)?([A-Za-z_]\w*)[ \t]*:?=[ \t]*new\([ \t]*(?:([A-Za-z_]\w*)\.)?([A-Za-z_]\w*)[ \t]*\)/
RECEIVER_CALL_RE = /(?<![\w.])([A-Za-z_]\w*)\s*\.\s*([A-Za-z_]\w*)\s*\(/

recv.Method( in a body: a call on a receiver.

RECEIVER_RE = /\Afunc\s*\(\s*(?:([A-Za-z_]\w*)\s+)?\*?\s*([A-Za-z_]\w*)/
REFERENCE_RE = /\A\s*((?:[A-Za-z_]\w*\s*\.\s*)*)([A-Za-z_]\w*)\s*(?:\(\s*\))?\s*\z/
RETURNED_CLOSURE = /\A\s*return\s+(?:(?:http\s*\.\s*)?HandlerFunc\s*\(\s*)?func\s*\(/
RETURNED_VALUE = /\A\s*return\s+(?:(?:http\s*\.\s*)?HandlerFunc\s*\(\s*)?([A-Za-z_]\w*)\s*\.\s*([A-Za-z_]\w*)\s*\)?\s*\z/
ROUTE_CALL_RE = /(?<!http)\.(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Get|Post|Put|Delete|Patch|Head|Options|Any|All|Handle|HandleFunc|Group|Route|Mount)\s*\(\s*"(?:[A-Z]+\s+)?\//

A route registration with a path literal (net/http's may lead with a method, "POST /items"): the stand-in for the same-file route-row check on a body declared in another file.

ROUTER_RETURN = /^\s*return\s+(.+?)\s*$/m
STRING_LITERAL = /"(?:[^"\\]|\\.)*"|`[^`]*`/
VALUE_REF_RE = /(?<![\w.])((?:[A-Za-z_]\w*\s*\.\s*)*[A-Za-z_]\w*)(?![\w.]|\s*[({.])/

A function value in a wrapped handler's text: a name or selector that is neither called nor a composite literal (a.UpdateSubscriber in pm(hasID(a.UpdateSubscriber), auth.PermX)).

Constructors

new(content : String, path : String, routes : Array(Noir::TreeSitterGoRouteExtractor::Route), scope : Scope | Nil = nil, handler_first : Bool = false)

handler_first: the handler is the first argument after the path and middleware follows it (Echo's GET(path, h, m...)), not the last.

Source

Class methods

reference(handler : String) : Tuple(String, String) | Nil

listA -> {"", "listA"}, h.List -> {"h", "List"}, RateLimit() -> {"", "RateLimit"}. Anything else (closures, calls with arguments, wrapped handlers) is not a plain reference, so nil.

Source
scope(file_contents : Hash(String, String), import_dirs : Hash(String, String) = Hash(String, String).new) : Scope

Test files are left out: an external foo_test package in the same directory may reuse a handler's name.

Source

Instance methods

claim?(index : Int32, line : String) : Bool

True when line (0-based index) is inside the body of a function a route names; the line is held for each_attribution.

Source
each_attribution

Yields every claimed line with each endpoint routed to its handler.

Source
each_bound_body

Yields each bound body's file and lines with the endpoints routed to it.

Source

The route's handler resolved to its definition: the last handler argument, which is the one that answers (everything before it is middleware), or the first one with handler_first. A wrapped handler names the function value it hands in. An inline closure there is the handler, named closure at the line it starts on; a middleware before a closure is never promoted, since route.GET("/home", CookieTool(), func(c) {...}) is not handled by CookieTool. A bare name bound to a closure in the registering function (isLive := func(c *gin.Context) {...}) names that closure. The definition the route's handler resolves to (see handler_for), for reading its request params; nil for an inline closure.

Source

Whether the route hands its path to a router a called function builds (mux.Handle("/v1/", api.NewHandler(store))): that router's own routes are reported, so the mount is not a route.

Source

Nested types