module Noir::TreeSitterGoRouteExtractor

Overview

Tree-sitter-backed Go route extractor.

Scope for this first cut: recognise the idioms shared by Gin / Echo / Fiber / Hertz / Iris — a router or group object with HTTP-verb methods attached (r.GET("/path", handler)), plus .Group("/prefix") chaining so nested groups resolve correctly.

Deliberately not covered yet (legacy regex extractor still handles these):

All of the above can grow into this extractor once the PoC is proven.

Extended Modules

Defined in:

miniparsers/go_route_extractor_ts.cr

Constant Summary

ANY_FAN_OUT_VERBS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]

The seven canonical HTTP methods Gin's r.Any, Echo's e.Any, Beego's * route etc. all stand for. Used by analyzer-level fan-out (see .fan_out_verbs).

HTTP_VERB_METHODS = Set {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "Get", "Post", "Put", "Delete", "Patch", "Head", "Options", "ANY", "Any", "All"}

HTTP verbs Gin/Echo/Fiber/etc. accept as method names on router objects. Mixed case is allowed because both r.GET(...) (Gin) and r.Get(...) (fiber, gin alt) appear in the wild.

NON_ROUTER_OPERANDS = Set {"gjson", "result", "results", "header", "headers", "Header", "Headers", "cookie", "cookies", "Cookie", "Cookies", "params", "Params", "values", "Values", "vars", "Vars", "url", "URL", "uri", "URI", "cache", "Cache", "db", "DB", "tx", "Tx", "conn", "Conn", "config", "cfg", "conf", "Config", "logger", "log", "client", "Client", "request", "Request", "req", "Req", "response", "Response", "resp", "Resp", "fixtures", "Fixtures", "slog"}

Common non-router identifiers in Go code that expose .Get(string) or .Post(...) style methods but emit values, not routes. The selector-expression walk emits a verb route on every match of <operand>.<HttpVerb>(stringLit, ...), so without this guard patterns like gjson.Get(json, "Files.0.UID"), header.Get("Content-Type"), or params.Get("user") become bogus /Files.0.UID, /Content-Type, /user endpoints.

Keep this list conservative — it only rejects names that are almost never used to hold a real router instance. Generic names like r, c, app, mux, engine are intentionally not included.

Class Method Summary

Instance Method Summary

Class Method Detail

def self.fan_out_verbs(verb : String) : Array(String) #

Returns the list of verbs to emit for a given extracted route verb. ANY / ALL (case-insensitive — verbs are uppercased before they reach this helper) expand to every canonical HTTP method so downstream output formats list each method explicitly instead of carrying a non-HTTP "ANY" verb that tools like SARIF/Postman can't ingest. Anything else passes through as a single-element list.


[View source]

Instance Method Detail

def extract_chi_routes(source : String, skip_functions : Set(String) = Set(String).new) : Array(Route) #

[View source]
def extract_gf_routes(source : String) : Array(Route) #

[View source]
def extract_goyave_statics(source : String) : Array(StaticPath) #

Goyave-style <router>.Static(&fs, "/prefix", false): the first /-prefixed string argument is the URL prefix; the disk path is derived by stripping its leading slash (matching the legacy extractor's behaviour, which used the same identifier for both).


[View source]
def extract_groups(source : String, external_groups : Hash(String, String) = Hash(String, String).new, group_method : String = "Group", group_aliases : Array(String) = [] of String) : Hash(String, String) #

Extracts only <name> := <parent>.<group_method>("/prefix") declarations. Used by the Go engine to run a cross-file fixpoint so group names defined in one file but referenced in another are known by the time #extract_routes runs on the referencing file.


[View source]
def extract_mux_statics(source : String) : Array(StaticPath) #

Mux-style <router>.PathPrefix("/x/").Handler(<... http.Dir("./x/") ...>). URL prefix comes from the PathPrefix arg; disk path from the http.Dir(...) call nested somewhere inside the Handler(...) argument expression.


[View source]
def extract_routes(source : String, external_groups : Hash(String, String) = Hash(String, String).new, group_method : String = "Group", handle_method : String | Nil = nil, handlefunc_methods : Bool = false, group_aliases : Array(String) = [] of String, extra_verbs : Array(String) = [] of String) : Array(Route) #

Parses source and returns every verb route it can resolve. external_groups supplies group prefixes defined in other files of the same Go package, so cross-file patterns like routes.go calling v1.GET(...) under a v1 := r.Group("/v1") declared in main.go resolve correctly. group_method is the method name used for grouping — Gin/Echo/Fiber/ Hertz use .Group(...), Iris uses .Party(...). Mux uses the special two-call chain <parent>.PathPrefix("/prefix").Subrouter(); pass "Subrouter" and the collector will peek through the chain to pull the prefix from the .PathPrefix(...) call. handle_method is the "method-first" shape some routers use (httprouter's .Handle("METHOD", "/path", handler)); set to nil to disable. handlefunc_methods enables mux's <router>.HandleFunc("/path", h).Methods("METHOD") chain — the outer call is .Methods(...), so this piggybacks on the walk rather than decode_verb_call.


[View source]
def extract_simple_statics(source : String, method_name : String = "Static") : Array(StaticPath) #

<router>.<method_name>("/prefix", "./dir", ...). The first two string args are taken as (url_prefix, disk_path). Covers the Gin/Echo/Fiber/Hertz/GoZero shape.


[View source]
def walk_chi_public(node : LibTreeSitter::TSNode, source : String, sink : Array(Route)) #

Exposes the closure-scoped walker against an arbitrary node (typically a function body captured elsewhere). Uses chi defaults.


[View source]