feat(rules): add MDS060 occurrence rule (plan 2607022118) - #758
Conversation
Counts each configured token or regex pattern within a scope unit (paragraph, heading-bounded section, or file) and flags when the count falls below `min` or exceeds `max`. Prose only — fenced and indented code blocks are excluded via CollectSectionParagraphsWithText. Fixes found during code review: - Add File: f.Path to every diagnostic (was empty, breaking CLI output and sort order) - Pre-lowercase paragraph text once per scope unit rather than once per (paragraph × token), keeping Check within the ≤10-alloc budget - Use FindAllStringIndex instead of FindAllString in countPattern to avoid allocating matched substrings just to count them - Validate min >= 0 in ApplySettings (negative min silently made the lower bound unreachable) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FgvajK8jHk49ncLChABAq2
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ng test coverage Extract per-setting helper methods from ApplySettings to satisfy the gocognit <= 30 limit. Also add tests for file-scope each mode, section-scope combined and pattern modes, type-error paths in ApplySettings helpers, and multi-section range-skip branches, raising unit coverage from 79% to 99%.
…en test countPattern is only called when r.Pattern != nil (all callers guard it), so the nil check was dead code per the project's defensive-code rule (branches must be driveable red/green). Remove it and add a test for the empty-token path in countToken to reach 100% statement coverage.
|
🟢 Merge Queue — picked up This PR is in the queue and will be batched with other Next: No action needed — you'll get another comment when CI starts on the batch. View merge queue run. |
|
🔵 Merge Queue — CI running Merged into batch branch Next: No action needed — you'll be notified when CI completes. |
|
✅ Merge Queue — merged This PR landed on Next: Done — nothing more to do here. |
Summary
occurrencerule: counts tokens or a regex pattern per scope unit (paragraph, section, file) and flags when the count falls belowminor exceedsmaxCollectSectionParagraphsWithTextenabled: falseby default); supportslists:wordlist integration viaWordlistTarget() = "tokens"Code review fixes applied
Four confirmed bugs found during xhigh code review and fixed before committing:
File: f.Pathmissing from diagnostics —diagEach/diagCombinedproduced diagnostics with emptyFile, breaking CLI output filename display and sort order; all 61 other rules setFile: f.PathcountTokencalledstrings.ToLower(text)once per token per paragraph; with N tokens and P paragraphs that's N×P allocations. Fixed by pre-lowercasing text once per scope unit before the token loop, keepingCheckwithin the ≤10-alloc budgetFindAllStringallocates matched substrings — switched toFindAllStringIndexwhich returns positions only, eliminating one string copy per matchmin < 0accepted silently — negativeminmade the lower-bound check always true;ApplySettingsnow returns an error formin < 0Files changed
internal/rules/occurrence/rule.go— rule implementationinternal/rules/occurrence/rule_test.go— 32 unit testsinternal/rules/MDS060-occurrence/README.md— rule documentationinternal/rules/MDS060-occurrence/bad/— 3 bad fixturesinternal/rules/MDS060-occurrence/good/— 2 good fixturesinternal/rules/all/all.go— import registrationinternal/integration/perrule_bench_test.go— alloc ceiling entry (MDS060: 4)internal/integration/testdata/rule_walk_audit.json— regenerated walk auditinternal/rulelayer/rule_walk_audit.json— copy kept in syncdocs/research/markdownlint-coverage/README.md,internal/rules/index.md— catalog regenerationGenerated by Claude Code