class

Analyzer::Go::GoEngine

Inherits Analyzer::Go::GoTestPath < Analyzer < FileHelper < Reference < Object

Constants

GO_HTTP_ROUTE_CALL_RE = /\.(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|QUERY|Get|Post|Put|Delete|Patch|Head|Options|Query|ANY|Any|All)\s*\(/

Class methods

go_test_file?(relative_path : String) : Bool

*_test.go is Go's hard-wired build-tag for test-only source. go build excludes these files entirely; only go test pulls them in. Real route handlers never live there, but framework repos (echo, chi, gin) park hundreds of e.GET("/...", ...) calls in *_test.go to exercise the router. Skip both in the cross-file pre-passes (so test-file groups don't pollute the production group map) and in each analyzer's per-file loop.

Also skip Go's toolchain-ignored directory prefixes: any path component starting with _ (_examples/, _assets/, _testdata/) is excluded by go build/go list outright. kataras/iris alone parks 392 phantom endpoints under _examples/...; they're documented apps, not production routes the framework ships. go help packages ignores a directory named testdata the same way: swaggo/swag parks 196 parser-input endpoints under testdata/....

Takes the scan-base-relative path (Analyzer#base_relative_path), never the absolute one. Go's toolchain rule is relative to the module, so on an absolute path a single _-prefixed directory anywhere above the scan base — ~/_work/repo on a self-hosted CI runner — excluded every .go file in the project.

relative_path from base_relative_path is /-rooted, so "/_" is exactly "a path segment starts with _" without the per-call split allocation. The leading-_ and testdata/ checks cover a caller that passes an unrooted relative path.

Source
strip_comments(text : String) : String

Blanks out // line comments and /* */ block comments, replacing them with spaces so every real line number stays put for PathInfo reporting. Without this a commented-out call reads as a live one — a // serveCmd.Flags().StringVar(&tok, "secret-token", ...) note registered a flag that the binary does not accept.

All three Go string forms are tracked so a // or /* inside a literal is never read as a comment opener: interpreted strings ("...", backslash escapes), rune literals ('.'), and raw strings (`...`) — the last take no escapes and may span newlines, hence raw_quotes.

Source

Instance methods

add_param_to_endpoint(param : Param, endpoint : Endpoint)
Source
collect_import_path_function_bodies(package_function_bodies : Hash(String, Hash(String, Noir::GoCalleeExtractor::FunctionBody))) : Hash(String, Hash(String, Noir::GoCalleeExtractor::FunctionBody))

Builds {import_path => {function_name => body}} for local Go packages under the configured base paths. This is gated on callee demand and reuses package_function_bodies, so default route scans do not pay for go.mod discovery. It lets generated routers like Hertz resolve feed.Feed when feed imports a sibling package from the same module.

Source

Builds {import_path => {method_name => [body, ...]}} for local Go packages under the configured base paths. This complements collect_import_path_function_bodies for route handlers referenced as method values on imported package instances, e.g. handler := api.NewHandler(svc); app.Get("/x", handler.Show).

Source
collect_package_controller_method_bodies(file_contents : Hash(String, String)) : Hash(String, Hash(String, Array(Noir::GoCalleeExtractor::FunctionBody)))

Per-directory {method_name => [FunctionBody, ...]} map for controller-style routes whose handler is referenced by method name (Beego's web.Router("/x", &Ctrl{}, "get:Method")). Lets the analyzer walk the controller method's body for callees even though the registration call doesn't pass the handler as an argument. Gated on callees_needed? so default scans pay nothing.

Source
collect_package_controller_methods(file_contents : Hash(String, String)) : Hash(String, Hash(String, Array(String)))

--- Beego controller-method pre-pass --------------------------------

Builds a per-directory {controller_type => [http_verb_methods]} map so mapping-less web.Router("/x", &Ctrl{}) registrations resolve to the exact methods the controller implements. Keyed by directory because Beego controllers and their router registrations share a Go package (== directory); a controller defined in a sibling file is still found.

Source
collect_package_function_bodies(file_contents : Hash(String, String)) : Hash(String, Hash(String, Noir::GoCalleeExtractor::FunctionBody))

--- Cross-file function body pre-pass --------------------------------

Walks every cached .go source in file_contents and collects top-level function_declaration nodes into a per-directory map so cross-file identifier-handler resolution in Noir::GoCalleeExtractor is O(1) at lookup time. The map is keyed by directory because Go's name resolution rules are scoped to a single package (== single directory), so we never have to worry about cross-package leakage.

Source
collect_package_groups_ts(group_method : String = "Group", import_marker : String | Array(String) | Nil = nil, string_seed : Hash(String, String) | Nil = nil) : Tuple(Hash(String, Hash(String, String)), Hash(String, String))

--- Tree-sitter group/route pre-pass --------------------------------

Does a cross-file fixpoint over every .go file in each package directory, extracting group declarations (x := r.Group("/x") and friends). Returns:

  • package_groups — per-directory {group_name => resolved_prefix}
  • file_contents — cached source strings keyed by path, so the per-file second pass doesn't read files twice

group_method is the method name used for grouping — "Group" for Gin/Echo/Fiber/Hertz (default), "Party" for Iris, "Subrouter" for Mux.

The fixpoint handles the routes.go calling v1.GET(...) under a v1 := r.Group("/v1") declared in main.go case — iterate until no new entries land. In-file declarations win over external ones. import_marker, when given, restricts the cross-file group/engine pre-pass to files that actually import the framework. Group variables (v1 := r.Group("/v1")) and root-engine bindings only ever appear in framework-importing files, so skipping the rest avoids parsing unrelated .go files — a large saving in mixed repos (e.g. an example monorepo where most dirs use other routers). file_contents is still populated for every file so the per-file route pass and callee pre-pass keep their read cache.

Source
expand_router_builders(content : String, lines : Array(String), path : String, expansions : Array(Tuple(String, Noir::TreeSitterGoRouteExtractor::RouterBuilder, Set(String))), callees_by_route : Hash(Int32, Array(Tuple(String, String, Int32))), named : Noir::GoNamedHandler | Nil, handle_method : String | Nil = nil, into emitted_to : Array(Endpoint) = result, string_seed : Hash(String, String) | Nil = nil, group_method : String = "Group", handlefunc_methods : Bool = false, literal_join : Bool = false, & : String, Endpoint -> )

Emits every expansions helper's routes once per call-site prefix (with the route's callees), then yields each (line, endpoint) pair of the helper body so the caller applies its framework's param patterns. The caller skips the helper bodies in its own line loop. Endpoints go to into (default: result).

Source
framework_package_dirs(file_contents : Hash(String, String), import_marker : String | Array(String)) : Set(String)

Per-package directories that import a target framework. Some real projects hide the concrete framework type behind a local interface, so the file that calls router.GET(...) may not import Gin/Echo itself.

Source
framework_route_source_candidate?(content : String, dir : String, framework_dirs : Set(String), import_marker : String | Array(String), extra_methods : Array(String), verb_calls : Bool = true) : Bool
Source
go_route_source_candidate?(content : String, extra_methods : Array(String), verb_calls : Bool = true) : Bool

Cheap gate before the tree-sitter route pass. Handler-only files often import Gin/Echo for *gin.Context / echo.Context, but they cannot emit endpoints unless they contain a route/static registration call.

verb_calls: false drops the shared .GET( / .Get( gate for a framework that registers no routes through verb-named methods (gorilla/mux: Router.Get(name) looks up a named route). In a package that imports the framework, .Get( / .Put( on caches, maps and clients otherwise sends nearly every file to the parser.

Source
go_string_seed

Project-wide string facts a route or group path can be built from: other packages' exported string constants (config.ApiUri) and one-string wrappers read from their definitions (conf.BaseUri("/s")), see TreeSitterGoRouteExtractor#extract_string_exports. A name two files define differently is left out. Pass the result as string_seed.

Source
lazy_package_bodies(file_contents : Hash(String, String)) : Noir::GoCalleeExtractor::LazyPackageBodies

Per-directory function and method tables, built for a directory only when an analyzer looks it up. Holds exactly what collect_package_function_bodies / collect_package_controller_method_bodies would store for that directory, and is empty unless callees are requested — see Noir::GoCalleeExtractor::LazyPackageBodies. Use it wherever the tables are only read through the ts_*_for_directory lookups; the import-path indexes iterate every directory and still need the eager maps.

Source
named_handler_scope(file_contents : Hash(String, String)) : Noir::GoNamedHandler::Scope

Lookup scope for Noir::GoNamedHandler: a named handler declared in another file of the route's package, or in a scanned package it imports (handlers.List). Not gated on callees: the handler body is where the route's request params are read. Packages are parsed only when a route names a handler outside its own file.

Source
prefetch_route_sources(file_contents : Hash(String, String), framework_dirs : Set(String), import_marker : String | Array(String), extra_methods : Array(String), verb_calls : Bool = true, &extract : String, String -> T) : Hash(String, T) forall T

Runs extract for every file the route pass will parse — the same test-file and framework_route_source_candidate? gates, evaluated here on the analyzer's own fiber — on Noir::WorkerThreads, and returns the results by path.

extract must be pure: tree-sitter extraction over the file's source and read-only tables built before this call. The route pass then takes each file's result from here instead of parsing it itself, and parses inline for any file missing (a gate it evaluates differently, or an extraction that raised on the pool) — so what the pass emits, and in what order, is unchanged.

Source
prefetch_sources(paths : Array(String), file_contents : Hash(String, String), &extract : String, String -> T) : Hash(String, T) forall T

extract for each of paths (all keys of file_contents) on Noir::WorkerThreads, by path; a path whose extraction raised is left out, so the caller computes it inline as before.

Source
read_package_file_contents

Read every .go file once into a {path => source} hash. Used by analyzers (Goyave) that don't otherwise call collect_package_groups_ts but still need the file_contents hash as input to collect_package_function_bodies.

Source
resolve_public_dirs(public_dirs : Array(Hash(String, String)))

A static mount's endpoint set is every file under its directory, images, fonts and SVGs included. Those come from the walk's media list (CodeLocator#media_files; the media filter keeps them out of the index); everything else from the index. Both are bounded by the scan base and skip the subtrees and --exclude-path entries the walk prunes. A single-file target (e.File("/robots.txt", "static/robots.txt")) is matched as is.

Source
resolve_router_builder_prefixes(file_contents : Hash(String, String), package_groups : Hash(String, Hash(String, String)), builder_type : String) : Hash(String, Hash(String, Set(String)))

Router-builder helpers (func addUserRoutes(rg *gin.RouterGroup)) receive their group as an argument, so the prefix lives at the call site (addUserRoutes(router.Group("/v1"))) and has to be grafted onto the helper's routes. builder_type is the group parameter type as written (*gin.RouterGroup, *echo.Group, *route.RouterGroup); its text gates the per-file parse and its last segment is the type name.

Builds {dir => {builder_fn => set(prefixes)}} by (1) collecting every helper across the directory's files and (2) resolving each call site F(arg) to the prefix arg denotes via the caller's directory group map. A call from another package (routes.F(api)) counts when the caller imports the helper's package; a method call (h.Register(v1), the qualifier not an import alias) resolves to a helper of that name in the caller's package or one it imports. A helper called with several groups accumulates each prefix; one with any unresolved call site (the root router, a computed value) is left out so the whole-file pass keeps its routes.

Source
router_builder_expansions(content : String, builder_type : String, dir_prefixes : Hash(String, Set(String)) | Nil, cross_file_groups : Hash(String, String)) : Array(Tuple(String, Noir::TreeSitterGoRouteExtractor::RouterBuilder, Set(String)))

The router-builder helpers defined in content to expand, with their resolved call-site prefixes. A helper whose group parameter name is also a package-level group (both named v1) is resolved by the whole-file pass by that name, so it is left alone only when its call sites hand it that same prefix. vikunja's registerAPIRoutesV2(e, a2) names its parameter a too, but is handed /api/v2, not the /api/v1 of RegisterRoutes' a: the name is the parameter's, scoped to the helper.

Source
static_dir_entry(source_path : String, static_path : String, file_path : String) : Hash(String, String)
Source
ts_controller_method_bodies_for_directory(package_controller_method_bodies : Hash(String, Hash(String, Array(Noir::GoCalleeExtractor::FunctionBody))), dir : String) : Hash(String, Array(Noir::GoCalleeExtractor::FunctionBody))
Source
ts_controller_method_bodies_for_directory(package_bodies : Noir::GoCalleeExtractor::LazyPackageBodies, dir : String) : Hash(String, Array(Noir::GoCalleeExtractor::FunctionBody))
Source
ts_controller_methods_for_directory(package_controller_methods : Hash(String, Hash(String, Array(String))), dir : String) : Hash(String, Array(String))

Returns the cross-file controller-method map for the given directory, or an empty map.

Source
ts_function_bodies_for_directory(package_function_bodies : Hash(String, Hash(String, Noir::GoCalleeExtractor::FunctionBody)), dir : String) : Hash(String, Noir::GoCalleeExtractor::FunctionBody)

Returns the cross-file function-body map for the given directory, or an empty map when the directory has no captured functions.

Source
ts_groups_for_directory(package_groups : Hash(String, Hash(String, String)), dir : String) : Hash(String, String)

Returns the cross-file group map for the given directory, or an empty map when the directory has no registered groups.

Source