- Zig 95.5%
- Shell 4.5%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
test / test (push) Successful in 28s
- build.zig: Run.addPassthruArgs() replaces b.args; the corpus option takes
the stdlib path as a LazyPath (addOptionPathUntracked of
std.Build.LazyPath.zig_lib), b.graph.zig_lib_directory being gone.
- std.zig.Ast.parse takes ParseOptions: `.zig` becomes `.{}` (mode .zig,
recovering, as before).
- CI: the runner's PATH finds Zig 0.16; run on /opt/zig-0.17.0.
`zig build test` and `zig build corpus` (the whole 0.17 stdlib) pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uZv7g8MJCfBfN4FRzUVtt
|
||
| .forgejo/workflows | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| build.zig | ||
| CLAUDE.md | ||
| LICENSE | ||
| README.md | ||
zigmap
Generates a code map of Zig source: the public API surface, with implementation
details stripped. Built on std.zig.Ast, so what it reports is what the
compiler parses.
Build
zig build # binary at zig-out/bin/zigmap
zig build test # snapshot tests (fast)
zig build corpus # sweep the Zig standard library
Requires Zig 0.16.
Usage
zigmap <file.zig>...
zigmap <directory>...
-h, --help Show help
--json Emit JSON instead of Zig syntax
Directories are walked recursively for .zig files. Build outputs
(zig-out/, zig-cache/) and hidden directories are skipped, and results are
sorted by path so output is stable enough to diff across runs.
Exits non-zero if any input could not be read or parsed.
What it extracts
Only pub declarations, plus imports:
- Module (
//!) and declaration (///) doc comments @importstatements- Types —
struct,enum,union,opaque— includingpacked/externlayout, enum backing types (enum(u8)), and union tag types (union(enum),union(Tag)) - Function signatures, including
extern/export/inline,callconv, generic parameters, and anonymous struct return types - Fields with their types,
align, and default values; enum members with explicit tag values - Constants and variables with type,
align,linksection,addrspace, andthreadlocal
Function bodies are dropped. Initializer expressions are summarized: struct
literals become .{...} and control-flow initializers (if, switch, block
expressions) become ..., since their value is not part of the API contract.
Example
//! Editor configuration.
const std = @import("std");
/// How aggressively to retry.
pub const Config = struct {
/// Display name.
name: []const u8 = "default",
retries: u32 = 3,
};
pub fn load(path: []const u8) !Config {
// ...
}
fn internalHelper() void {}
maps to:
//! Editor configuration.
const std = @import("std");
/// How aggressively to retry.
pub const Config = struct {
/// Display name.
name: []const u8 = "default",
retries: u32 = 3,
};
pub fn load(path: []const u8) !Config;
The output is Zig-flavored rather than compilable — bodyless pub fn f() void;
and = ... are deliberate, so the map reads like a header.
JSON
--json emits the same information as a structured document:
{"files": [{
"path": "config.zig",
"doc": "Editor configuration.",
"imports": [{"name": "std", "path": "std"}],
"decls": [
{"kind": "struct", "name": "Config", "doc": "How aggressively to retry.",
"members": [
{"kind": "field", "name": "name", "type": "[]const u8",
"value": "\"default\"", "doc": "Display name."}
]},
{"kind": "fn", "name": "load", "signature": "pub fn load(path: []const u8) !Config"}
]
}]}
Both formats are produced by the same traversal, so they cannot report different API surfaces.
Tests
zig build test — fast and hermetic. tests/fixtures/ holds inputs covering
each construct; tests/snapshots/ holds the expected Zig and JSON output for
each, compared byte-for-byte. Also checks that private declarations stay
excluded and that malformed source raises an error rather than being skipped.
After an intentional output change, regenerate and review the diff:
zig build && zig build update-snapshots
zig build corpus — maps the Zig standard library (~550 files) in both
formats and asserts no output is malformed: no value rendered as a bare
keyword, no truncated signature, no declaration that lost its name. It runs on
testing.allocator, so it doubles as a leak check.
The standard library is the corpus because it ships with the compiler: no
network, no vendored tree, and it always matches the Zig building the project.
Hand-written fixtures only cover constructs someone thought to write down —
this sweep is what caught two node-data crashes (.unwrap_optional and
.grouped_expression carry node_and_token, not node) that every fixture
missed. It's slower, so it's a separate step from test; run it after touching
the AST traversal.