- Zig 98.9%
- Shell 1.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
The hand-written fixtures only cover constructs someone thought to write down. The Zig standard library is a much larger corpus that needs no network and no vendored tree: it ships with the compiler and always matches the Zig building this project. Running it immediately found two hard crashes in writeNodeSource. Both .unwrap_optional and .grouped_expression carry `node_and_token` data, but the code read the `node` variant, so mapping any `expr.?` or `(expr)` in a value position panicked on the union field access. No fixture produced those shapes. `zig build corpus` now maps all ~550 std lib files in both output formats and asserts the output is well formed: 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. Verified it fails when either crash is reintroduced, while the fixture snapshots still pass. Kept as a separate step from `test` so the common path stays fast: `zig build test` is 0.3s, the corpus sweep is ~30s. Also gitignore test/, the ad-hoc corpus copied in from another project. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
| 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.