Skip to content

fix: reduce MODICUM lint false positives#9

Merged
EugOT merged 1 commit into
mainfrom
test/ziglint-modicum-false-positive-fixes-20260701
Jul 1, 2026
Merged

fix: reduce MODICUM lint false positives#9
EugOT merged 1 commit into
mainfrom
test/ziglint-modicum-false-positive-fixes-20260701

Conversation

@EugOT

@EugOT EugOT commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Walk switch and switch-case AST nodes so nested declarations are visible to lint rules.
  • Preserve syntactic imported-type fallback when semantic type resolution cannot classify a node.
  • Avoid false positives for Self/enclosing container types, exported function names, callable struct init arguments, and comment/raw-string/string-literal line-length cases.
  • Document the adjusted Z024 behavior.

Validation

  • git diff --check
  • zig fmt --check src/Linter.zig src/main.zig
  • zig build --summary failures
  • zig build test --summary failures --test-timeout 30s
  • pre-commit hooks: gitleaks and veles, no findings
  • pre-push hooks: gitleaks and veles, no findings

MODICUM integration context

This is the linter-side fix needed before MODICUM can replace its stale pinned zig-pkg/ziglint-0.5.2 dependency with the updated EugOT/ziglint mainline.

Teach the linter to walk switch nodes, preserve syntactic imported-type fallbacks, ignore exported function names for Z001, and avoid Z024 reports for comments/raw strings/string literals.

This fixes stale false positives seen when validating MODICUM against the local Zig 0.16 QC stack.

Validation: git diff --check; zig fmt --check src/Linter.zig src/main.zig; zig build --summary failures; zig build test --summary failures --test-timeout 30s.
Copilot AI review requested due to automatic review settings July 1, 2026 11:27
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • review-ready
🚫 Excluded labels (none allowed) (2)
  • skip-review
  • no-coderabbit

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: f41e93e9-c625-400a-84ad-27886148a5c8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/ziglint-modicum-false-positive-fixes-20260701

Comment @coderabbitai help to get the list of available commands.

@EugOT EugOT merged commit 5c53b0b into main Jul 1, 2026
8 checks passed
@EugOT EugOT deleted the test/ziglint-modicum-false-positive-fixes-20260701 branch July 1, 2026 11:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces MODICUM-facing false positives by improving AST traversal (so nested declarations inside switch/case bodies are visible), preserving syntactic fallbacks when semantic type resolution is inconclusive, and refining several heuristics for name/type/line-length related rules. It also adds targeted tests and updates Z024 documentation to reflect the new behavior around literal/comment lines.

Changes:

  • Extend AST child-walking to include switch and switch_case* nodes and refine public/imported type classification logic.
  • Reduce false positives in rules around exported function names, container Self/enclosing type names, callable struct-init call arguments, and deinit poisoning patterns.
  • Adjust Z024 to skip comment-only, raw-string, and string-literal lines, with expanded tests and docs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/main.zig Skip linting non-.zig file paths and add a focused unit test for the new path filter.
src/Linter.zig Main linting behavior changes: improved AST traversal, semantic/syntactic fallback handling, and multiple false-positive reductions with added tests.
docs/rules/Z024.md Document updated Z024 semantics (skipping comments/raw strings/string-literal lines) and add an example rationale.
Comments suppressed due to low confidence (1)

src/Linter.zig:385

  • ChildList was expanded to 32 items, but append() still hard-caps at self.len < 8, so only the first 8 children are tracked. This undermines the new switch/switch-case traversal and can leave parent_map incomplete for nodes with >8 children.
    fn append(self: *ChildList, item: Ast.Node.Index) void {
        if (self.len < 8) {
            self.items[self.len] = item;
            self.len += 1;
        }
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Linter.zig
Comment on lines +1800 to +1805
fn isDestroySelfCall(self: *Linter, node: Ast.Node.Index, param_name: []const u8) bool {
const source = self.getNodeSource(node);
if (std.mem.indexOf(u8, source, ".destroy(") == null) return false;
if (std.mem.indexOf(u8, source, param_name) == null) return false;
return true;
}
Comment thread src/Linter.zig
Comment on lines +3055 to +3069
fn hasStringLiteral(line: []const u8) bool {
var escaped = false;
for (line) |c| {
if (escaped) {
escaped = false;
continue;
}
if (c == '\\') {
escaped = true;
continue;
}
if (c == '"') return true;
}
return false;
}
Comment thread src/Linter.zig
Comment on lines +2614 to +2618
fn typeDeclHasCallableMembers(self: *Linter, type_node: Ast.Node.Index) bool {
const type_name = self.getTypeNodeName(type_node) orelse return false;

for (0..self.tree.nodes.len) |i| {
const node: Ast.Node.Index = @enumFromInt(i);

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11f96a156e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Linter.zig
Comment on lines +377 to 380
items: [32]Ast.Node.Index = undefined,
len: usize = 0,

fn append(self: *ChildList, item: Ast.Node.Index) void {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep ChildList's append limit in sync

Switch traversal added in this patch appends the condition plus every case through ChildList, but append still stops at self.len < 8. A switch with 8+ arms (or a case with many values) silently drops later children, so parent_map/visitNode won't reach declarations in those arms and the MODICUM false positives can still occur past the seventh arm; use the actual array length or handle switch cases as an unbounded child list.

Useful? React with 👍 / 👎.

Comment thread src/Linter.zig
Comment on lines +1802 to +1804
if (std.mem.indexOf(u8, source, ".destroy(") == null) return false;
if (std.mem.indexOf(u8, source, param_name) == null) return false;
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require destroy to target the receiver

This exemption accepts any final statement whose source contains .destroy( and the receiver name as a substring. In a deinit such as self.* = undefined; allocator.destroy(self.child); (or allocator.destroy(not_self)), Z030 is suppressed even though the final call is not destroying the receiver and may use the receiver after poisoning; parse the call and require the destroy argument to be exactly the receiver parameter.

Useful? React with 👍 / 👎.

Comment thread src/Linter.zig
var buf: [2]Ast.Node.Index = undefined;
const struct_init = self.tree.fullStructInit(&buf, node) orelse return;
const type_node = struct_init.ast.type_expr.unwrap() orelse return;
if (in_call_arg and self.typeDeclHasCallableMembers(type_node)) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not skip Z010 just because a struct has methods

With this early return, bar(Foo{}) is no longer reported whenever Foo declares any function, even if bar has a concrete Foo parameter and bar(.{}) is valid. The callable-member exception only applies to generic/interface-style call sites, so suppressing based solely on the type declaration creates broad Z010 false negatives for ordinary structs with methods.

Useful? React with 👍 / 👎.

@kilo-code-bot

kilo-code-bot Bot commented Jul 1, 2026

Copy link
Copy Markdown

Code Review Roast 🔥

Verdict: 5 Issues Found | Recommendation: Merge was already completed; issues remain unresolved

Overview

Severity Count
⚠️ warning 3
💡 suggestion 2
Issue Details (click to expand)
File Line Roast
src/Linter.zig 380 ChildList buffer expanded but append limit still 8, drops switch children silently
src/Linter.zig 1804 isDestroySelfCall() uses substring matching, false positives on destroy calls
src/Linter.zig 2537 Z010 skip for method structs too broad, creates false negatives
src/Linter.zig 2618 typeDeclHasCallableMembers O(n²) full tree scan on every call arg
src/Linter.zig 3069 hasStringLiteral() doesn't ignore comment portion, bypasses Z024

🏆 Best part: Oh wait, the switch-case walking logic is actually well-structured. I had my flamethrower warmed up but that part's clean.

💀 Worst part: The ChildList mismatch between [32] array and < 8 limit—this silently breaks Z019 for switches with 8+ arms. A switch that doesn't get fully walked is a switch that doesn't get fully linted.

📊 Overall: Like serving a cake with missing ingredients—good intentions but the ChildList limit forgot to scale up with the buffer size.

Files Reviewed (3 files)
  • src/Linter.zig - 5 issues
  • src/main.zig - clean
  • docs/rules/Z024.md - clean

Reviewed by laguna-m.1-20260312:free · Input: 822.4K · Output: 11.2K · Cached: 2M

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants