smithery/rockorager

new-rule

Add a new linter rule to ziglint. Use when creating a new Z-rule, implementing a lint check, or adding code analysis.

Installation

$ npx skills add smithery/rockorager --skill new-rule

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,992 B
  • docs SUMMARY.md 133 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Adding a New Linter Rule

When adding a new rule, you MUST modify these files in order:

1. Determine the Rule Number

Check src/rules.zig to find the next available rule number. Rules use the format ZXXX (e.g., Z001, Z025).

2. Add Rule Definition (src/rules.zig)

Add enum variant

In the Rule enum, add the new rule in numerical order:

pub const Rule = enum(u16) {
    // ... existing rules
    ZXXX = XXX,  // Add your rule here
};

Add config type (if rule has parameters)

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 }),
        // ...
    };
}

Add error message

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 }),

3. Implement the Check (src/Linter.zig)

Add check function

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);
}

Call from visitor

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
}

4. Add Tests (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));
}

5. Update Documentation

IMPORTANT: Do not skip these steps!

Add to README.md

Add the rule to the rules table in README.md, maintaining numerical order:

| ZXXX | Brief description of what the rule checks |

Create Rule Documentation (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:

  • Code blocks with // expect: ZXXX comments are tested to trigger the rule
  • Code blocks without expect comments are tested to produce no warnings
  • These examples are validated by zig build test - if the rule changes and examples become inaccurate, the build fails
  • Include both bad (triggering) and good (passing) examples

6. Run Tests

zig build test

Common Patterns

Helper functions available in Linter.zig:

  • isValidFunctionName(name) - Check camelCase
  • isPascalCase(name) - Check PascalCase
  • isSnakeCase(name) - Check snake_case
  • isTypeExpression(node) - Check if node is a type
  • isBuiltinType(name) - Check if name is a builtin type

AST access:

  • self.tree.nodeTag(node) - Get node type
  • self.tree.tokenSlice(token_idx) - Get token text
  • self.tree.tokenLocation(0, token_idx) - Get line/column
  • self.tree.fullVarDecl(node) - Parse variable declaration
  • self.tree.fullFnProto(&buf, node) - Parse function prototype

Encoded context (for multi-part messages):

// 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);