Noir::ZigCalleeExtractor
Inherits Noir::CalleeExtractorBase
Shared structural helper for the Zig framework analyzers (jetzig, zap, httpz, tokamak). Zig has no vendored tree-sitter grammar in noir, so the analyzers lean on this hand-rolled miniparser for two jobs:
function_table/function_bodies— index everyfn name(...) { … }in a source file so a route whose handler lives in a named function (httpzrouter.get("/x", getUser, .{}), tokamak.get("/", hello)) can resolve that handler's body for callee extraction.callees_for_body— pull the 1-hop function calls out of a handler body, used by--include-calleeand--ai-context.
The scanners blank out comments and string/char literals first
(strip_non_code) so a call-shaped token inside a doc-string or a //
comment is never surfaced as a phantom callee, and they address the
source through an Array(Char) (O(1) indexing) so a single non-ASCII byte
anywhere in the file can't turn the per-character loops into O(n²).
Constants
A call expression: an optional @ (builtin) + identifier, then any number
of .identifier accessors, immediately followed by (. The lookbehind
stops the match from starting in the middle of an identifier or chain, so
foo.bar( yields foo.bar once, not also bar.
(?:pub )? (modifiers)* fn name ( — captures the function name. The
extern "C" calling-convention string is already blanked by
strip_non_code, so only the keyword spacing has to be tolerated.
Zig keywords that are followed by ( in normal code (if (…),
while (…), switch (…), catch (…)) and would otherwise be reported
as callees. fn/return/try etc. never precede a call paren directly
but are kept here for clarity.
Receiver roots whose calls are pure noise for endpoint review context.
std.* (std.debug.print, std.mem.eql, std.fmt.*) appears in nearly
every handler and drowns the meaningful callees.
test { … } / test "name" { … } block opener. Route registrations inside
a test block are unit-test fixtures — and, in a framework's own source
vendored as a loose file (modules/httpz.zig, modules/router.zig), its
self-tests — never runtime endpoints.
A .zig file that belongs to a vendored copy of a framework checked into
the source tree (zig-pkg/zap/…, src/deps/tokamak/…) rather than to the
application. Such trees ship the framework's own tests and examples
(zap/src/tests/test_auth.zig's .path = "/test", tokamak/example/'s
@"GET /:name"), whose route literals would otherwise surface as phantom
app endpoints. The standard fetched-dependency cache (.zig-cache) is
already pruned upstream; this matches the manual-vendoring layouts —
a vendor directory immediately followed by a framework package directory.
Instance methods
Find the index of the delimiter matching the opener at open_index.
Returns nil on imbalance. Operates on the already-stripped char array so
braces inside strings/comments are gone.
Convenience map keyed by simple function name. When two functions share a
name (e.g. several zap endpoint structs each defining get) the first
wins; callers that need struct scoping should filter function_table
by offset instead.
Same as function_table but reuses an already-stripped char array from
prepare / strip_non_code, so callers that already paid for a strip
don't re-walk the file.
Line counter for Regex::MatchData#begin positions. Avoids the
text.chars allocation every endpoint emission used to pay for. #begin
is a CHAR index, so convert it to a byte offset before the byte walk —
otherwise a multi-byte char before the match (e.g. inside a preserved
string literal) makes the char index under-shoot the true byte position
and the reported line number comes out too small (regression class of #2297).
\n is single-byte ASCII, so the byte walk itself stays correct and
allocation-free relative to Array(Char).
Like strip_non_code but keeps the contents of double-quoted string
literals — route paths live inside "…", so the framework analyzers scan
this form to read the URL while still ignoring routes that sit in a
comment, a \\ multiline doc-string, or a char literal.
Byte ranges of test blocks, brace-matched on the string-blanked source so a
{/} inside a literal can't throw the matching off. in_test_block?
then lets an analyzer drop a route whose registration sits inside one.