ワンクリックで
new-rule
Add a new linter rule to ziglint. Use when creating a new Z-rule, implementing a lint check, or adding code analysis.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Add a new linter rule to ziglint. Use when creating a new Z-rule, implementing a lint check, or adding code analysis.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | new-rule |
| description | 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);