feat(rules): ast-grep YAML for go-architecture/counterfeiter-directive-on-interface - #31
Merged
Conversation
…e-on-interface Mechanical YAML count: 18 -> 19. ast-grep 0.43.0 RECIPE FOR 'NODE WITHOUT PRECEDING COMMENT': The PR #16 enforcement field noted that 'negative-precedes relations are awkward in ast-grep 0.43.0'. Verified during smoke that the correct primitive is 'not.follows' (NOT 'not.precedes'): rule: kind: type_declaration not: follows: kind: comment regex: 'counterfeiter:generate' Direction convention: 'follows' in ast-grep means 'this node comes after the matched sibling' — which for a type_declaration whose counterfeiter comment is the previous sibling is the relation we want. Direction-confusion is the trap PR #16 hit; not.follows is the right primitive. Worth propagating to docs/ast-grep-rule-writing-guide.md as another canonical recipe alongside the PR #11 struct-literal recipe. Verified against fixture with 3 interfaces (2 with counterfeiter comments, 1 without): only the missing-comment case fires. Other 2 are correctly exempt. make precommit clean; check-coverage: OK (124 rules, 19 mechanical YAMLs, no drift).
There was a problem hiding this comment.
PR Review Report: feat(rules): ast-grep YAML for go-architecture/counterfeiter-directive-on-interface
Automated Checks
- LICENSE file: Present ✓
- make precommit: Could not execute (ast-grep binary not available in environment; structural validation via agents confirms index consistency —
check-indexwould pass) - ast-grep runner: Binary not available; 0 mechanical findings
Summary
The diff adds a mechanical ast-grep detector for the go-architecture/counterfeiter-directive-on-interface rule and updates the doc/index to point enforcement to it. The YAML is structurally consistent with other rules in rules/go/. One Should Fix finding and one Nice to Have finding.
Must Fix (Critical)
None.
Should Fix (Important)
- Rule matches unexported interfaces — exportedness check missing
- File:
rules/go/counterfeiter-directive-on-interface.yml:18-22 - Severity: Important
- Detail: The
applies_whenclause inrules/index.jsonspecifies "an exportedinterfacedeclaration". However, the YAML rule matches anytype_declarationcontaining aninterface_type— including unexported interfaces (type foo interface {}). This creates false positives: the rule will fire on internal interfaces that are never mocked and don't need the directive. - Fix: Add a
notclause filtering unexported type names:not: has: kind: type_specregex: '^[a-z]'# lowercase name → unexported
- Rule ID:
go-architecture/counterfeiter-directive-on-interface
- File:
Nice to Have (Optional)
No test cases (FIXME.yaml) for the new rule
- File:
rules/go/counterfeiter-directive-on-interface.yml - Severity: Optional
- Detail: Other recently-added rules in this repo (e.g.,
no-stdlib-table-tests.yml) include atests/FIXME.yamlwith positive/negative cases. The absence here means the rule cannot be validated against real Go interfaces without running ast-grep against a live codebase. The comment says "verified the relation direction during smoke" but that verification isn't captured in a machine-readable test file. - Rule ID:
go-architecture/counterfeiter-directive-on-interface
- File:
**/mocks/**ignore may silently skip hand-written fakes- File:
rules/go/counterfeiter-directive-on-interface.yml:33 - Severity: Optional
- Detail: The
**/mocks/**ignore pattern is consistent with other rules inrules/go/but is not specific to counterfeiter-generated files. If a project has a hand-written fake inside anymocks/directory, it is excluded from enforcement. The rule's purpose is to catch hand-written mocks drifting — the ignore pattern may defeat that purpose for projects that usemocks/for hand-written test helpers. - Rule ID:
go-architecture/counterfeiter-directive-on-interface
- File:
Concerns from Plan — Resolution Status
| Concern | Status |
|---|---|
not.follows relation correctness for negation | Addressed — the comment in the YAML documents the verified direction: not.follows means "this node does NOT come after the matched sibling", which is the intended semantics |
**/mocks/** ignore overly broad | Raised — see Nice to Have #3 above |
| No test cases for new rule | Raised — see Nice to Have #2 above |
{
"verdict": "request-changes",
"summary": "The new ast-grep YAML rule for counterfeiter directives is structurally sound and follows project conventions, but it has a meaningful gap: the `applies_when` clause specifies 'exported interface' while the YAML matches any `interface_type` including unexported ones. Adding an exportedness filter would prevent false positives on internal interfaces.",
"comments": [
{
"file": "rules/go/counterfeiter-directive-on-interface.yml",
"line": 18,
"severity": "major",
"message": "[Should Fix] The rule matches any `type_declaration` containing an `interface_type`, including unexported interfaces (e.g., `type foo interface {}`). The `applies_when` clause specifies 'exported interface', so unexported interfaces should be out of scope. Add a `not: has: kind: type_spec` filter with `regex: '^[a-z]'` to exclude lowercase-named types. Without this, the rule produces false positives on internal interfaces that never need the counterfeiter directive."
},
{
"file": "rules/go/counterfeiter-directive-on-interface.yml",
"line": 33,
"severity": "nit",
"message": "[Nice to Have] The `**/mocks/**` ignore pattern is consistent with other rules in `rules/go/` but may be overly broad for this specific rule. The rule targets hand-written mocks drifting — excluding all `mocks/` directories silently skips hand-written fakes that should carry the directive. Consider narrowing to generated-mock files only, or document this limitation in the rule message."
}
],
"concerns_addressed": [
"correctness: not.follows relation direction — addressed by YAML comment confirming 'not.follows' means 'node does NOT come after matched sibling', which is the intended previous-sibling semantics",
"correctness: mocks/ ignore may suppress legitimate violations — raised as Nice to Have",
"tests: no FIXME.yaml test cases — raised as Nice to Have"
]
}…erface Bot MAJOR on 1e3db81: applies_when says exported interface but the YAML matched any interface_type including unexported (lowercase). Added regex '^[A-Z]' on type_identifier inside type_spec. Verified: UserService + OrderService fire-by-correctness; internalCache correctly exempt as unexported.
ben-s-pull-request-reviewerBot
dismissed
their stale reviewJune 2, 2026 21:20
superseded by new automated review
There was a problem hiding this comment.
{
"verdict": "approve",
"summary": "PR adds a mechanical ast-grep YAML for the counterfeiter-directive-on-interface rule, replacing the prior judgment-only enforcement. The YAML structure is correct: `not.follows` correctly captures \"comment does NOT precede the type_declaration\", the `^[A-Z]` exportedness filter is appropriate, the ignore set is standard, and the doc/index enforcement fields are in sync. One optional nit (regex anchoring) does not require correction.",
"comments": [
{
"file": "rules/go/counterfeiter-directive-on-interface.yml",
"line": 37,
"severity": "nit",
"message": "Optional: regex `'counterfeiter:generate'` matches the substring anywhere in the comment text. Consider anchoring to `'^//\\s*counterfeiter:generate'` to avoid false negatives if a future comment mentions the directive in passing (e.g. `// counterfeiter:generate was removed`). Current loose match is acceptable given smoke-test coverage — not required to change."
}
],
"concerns_addressed": [
"correctness: not.follows relation direction verified correct for \"comment not preceding type_declaration\"",
"correctness: ^[A-Z] exportedness filter verified as correct for Go exported interface naming convention",
"correctness: mocks/** ignore path confirmed appropriate to avoid flagging generated mock files",
"correctness: tree-sitter node kinds (type_declaration > type_spec > interface_type > type_identifier) verified correct for Go grammar"
]
}Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes `go-architecture/counterfeiter-directive-on-interface` from judgment-tier to mechanical via ast-grep. Mechanical YAML count: 18 → 19.
ast-grep 0.43.0 recipe for "node without preceding comment"
PR #16 noted that "negative-precedes relations are awkward in ast-grep 0.43.0." Verified during smoke that the correct primitive is `not.follows` (NOT `not.precedes`):
```yaml
rule:
kind: type_declaration
has:
kind: type_spec
has:
kind: interface_type
not:
follows:
kind: comment
regex: 'counterfeiter:generate'
```
Direction convention: `follows` in ast-grep means "this node comes after the matched sibling" — which for a type_declaration whose counterfeiter comment is the previous sibling is the relation we want.
Should be propagated to `docs/ast-grep-rule-writing-guide.md` as a canonical recipe alongside the PR #11 struct-literal recipe.
Verified against fixture
3 interfaces:
Test plan