Add a new linter rule to ziglint. Use when creating a new Z-rule, implementing a lint check, or adding code analysis.
When adding a new rule, you MUST modify these files in order:
Check src/rules.zig to find the next available rule number. Rules use the format ZXXX (e.g., Z001, Z025).
src/rules.zig)In the Rule enum, add the new rule in numerical order:
pub const Rule = enum(u16) {
// ... existing rules
ZXXX = XXX, // Add your rule here
};
If the rule needs configuration beyond just enabled, add a case in ConfigType():
fn ConfigType(comptime self: Rule) type {
return switch (self) {
.ZXXX => RuleConfig(true, struct { my_param: u32 = 100 }),
// ...
};
}
In writeMessage(), add the message formatting. Use ANSI colors via the provided variables (y for yellow, r for reset):
.ZXXX => try writer.print("description with {s}'{s}'{s} highlighted", .{ y, context, r }),
src/Linter.zig)Create a function following the naming pattern checkXxx:
fn checkMyRule(self: *Linter, node: Ast.Node.Index) void {
// Skip if rule is disabled
if (!self.ruleEnabled(.ZXXX)) return;
// Get node data from AST
const tag = self.tree.nodeTag(node);
// ... your logic
// Report violation
const loc = self.tree.tokenLocation(0, token);
self.report(loc, .ZXXX, context_string);
}
Add your check to the appropriate place:
For per-node checks, add to visitNode() switch:
.fn_decl => self.checkFnDecl(node),
.my_node_type => self.checkMyRule(node), // Add here
For whole-file checks, call from lint() directly:
pub fn lint(self: *Linter) void {
// ... existing checks
self.checkMyWholeFileRule(); // Add here
}
src/Linter.zig)Add tests at the bottom of the file following this pattern:
test "ZXXX: detect violation case" {
var linter: Linter = .init(std.testing.allocator,
\\const x = bad_code;
, "test.zig", null);
defer linter.deinit();
linter.lint();
try std.testing.expectEqual(1, linter.diagnosticCount(.ZXXX));
}
test "ZXXX: allow valid case" {
var linter: Linter = .init(std.testing.allocator,
\\const x = good_code;
, "test.zig", null);
defer linter.deinit();
linter.lint();
try std.testing.expectEqual(0, linter.diagnosticCount(.ZXXX));
}
IMPORTANT: Do not skip these steps!
Add the rule to the rules table in README.md, maintaining numerical order:
| ZXXX | Brief description of what the rule checks |
docs/rules/ZXXX.md)Create a detailed documentation file with executable examples:
---
rule: ZXXX
title: Brief description of the rule
enabled: true
---
# ZXXX: Brief description
Explanation of what this rule checks and why it matters.
## Examples
### Bad
\`\`\`zig
// expect: ZXXX
code_that_triggers_the_rule();
\`\`\`
### Good
\`\`\`zig
code_that_passes();
\`\`\`
## Configuration
(Include this section if the rule has configurable parameters)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `max_length` | `u32` | `120` | Maximum allowed line length |
Example `.ziglint.zon`:
\`\`\`zig
.rules = .{
.ZXXX = .{ .max_length = 80 },
},
\`\`\`
## Rationale
Why this rule exists and what problems it prevents.
Key points:
// expect: ZXXX comments are tested to trigger the rulezig build test - if the rule changes and examples become inaccurate, the build failszig build test
isValidFunctionName(name) - Check camelCaseisPascalCase(name) - Check PascalCaseisSnakeCase(name) - Check snake_caseisTypeExpression(node) - Check if node is a typeisBuiltinType(name) - Check if name is a builtin typeself.tree.nodeTag(node) - Get node typeself.tree.tokenSlice(token_idx) - Get token textself.tree.tokenLocation(0, token_idx) - Get line/columnself.tree.fullVarDecl(node) - Parse variable declarationself.tree.fullFnProto(&buf, node) - Parse function prototype// Use null byte as separator
const context = alloc.alloc(u8, part1.len + 1 + part2.len);
@memcpy(context[0..part1.len], part1);
context[part1.len] = 0;
@memcpy(context[part1.len + 1..], part2);
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer