Analyzer::Go::GoEngine
Inherits Analyzer::Go::GoTestPath < Analyzer < FileHelper < Reference < Object
Constants
Class methods
*_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.
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.
Instance methods
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.
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).
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.
--- 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.
--- 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.
--- 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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Returns the cross-file controller-method map for the given directory, or an empty map.
Returns the cross-file function-body map for the given directory, or an empty map when the directory has no captured functions.