Add negated-assert checker and extend require-error: detect and fix if !assert.Xxx { return } patterns - #297
Open
mmorel-35 wants to merge 1 commit into
Open
Add negated-assert checker and extend require-error: detect and fix if !assert.Xxx { return } patterns#297mmorel-35 wants to merge 1 commit into
negated-assert checker and extend require-error: detect and fix if !assert.Xxx { return } patterns#297mmorel-35 wants to merge 1 commit into
Conversation
ccoVeille
approved these changes
Mar 15, 2026
Copilot AI
added a commit
to mmorel-35/testifylint
that referenced
this pull request
May 28, 2026
…ions in if conditions
mmorel-35
pushed a commit
to mmorel-35/testifylint
that referenced
this pull request
May 28, 2026
…ions in if conditions
mmorel-35
force-pushed
the
negated-assert
branch
2 times, most recently
from
August 7, 2026 12:01
abaf8d7 to
7799b1e
Compare
…ix `if !assert.Xxx { return }` patterns
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
mmorel-35
force-pushed
the
negated-assert
branch
from
August 7, 2026 17:08
9887fc9 to
53d98e2
Compare
This was referenced Aug 15, 2026
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 free
to 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.
The
require-errorchecker missed!assert.xxx()inifconditions because*ast.UnaryExprwas not handled in theinIfConddetection stack walk. This caused the "last assertion" skip logic to produce inconsistent results. Additionally, theif !assert.Xxx { return }pattern is a general anti-pattern applicable to any assertion, not just error ones — so a dedicated checker was created to cover it broadly.require-errorwas also extended to independently detect and fix this pattern for error assertions, so it works correctly even when used as the only enabled checker.Changes
Bug fix —
*ast.UnaryExprdetection in if conditions (require_error.go)!assert.xxx()(stack:IfStmt → UnaryExpr → CallExpr) and!assert.xxx() || …(stack:IfStmt → BinaryExpr → UnaryExpr → CallExpr) asinIfCond, ensuringrequire-errorcorrectly skips these patterns without false positives.ParenExprnodes are treated as transparent throughout the stack walk and the collect functions, soif (!assert.NoError(...)) { return }andif !(assert.NoError(...)) { return }are also recognised.New dedicated checker —
negated-assert(internal/checkers/negated_assert.go)Detects and provides a
SuggestedFixfor any negated assertion in an if condition guarding areturnorcontinue:And the compound
||variant:A pattern is only fixed when all top-level conditions are negated testify assert package calls joined by
||, the body is a singlereturn/continue, there is noelse, theifhas no init clause, and theifis not anelse ifbranch (which would leave a danglingelse). Patterns using&&, with complex bodies, with an init clause, or inside goroutines/HTTP handlers/test cleanup are skipped. Therequireimport is added automatically if absent.Extended
require-error— negated error-assertion if-patternsrequire-errornow independently detects and fixesif !assert.ErrorXxx { return/continue }patterns for error assertions, so the pattern is always caught regardless of whethernegated-assertis also enabled:The scope split is:
require-errorhandles error assertions (NoError,Error,ErrorIs,ErrorAs,EqualError,ErrorContains,NotErrorIs) in negated-if patterns — within its existing scope.negated-asserthandles all assertions (including non-error ones) in negated-if patterns.When both checkers are enabled, both independently fire on error assertion negated-if patterns.
Robustness fixes
negated-assertandrequire-errorStage 2a now skipifstatements with an init clause (e.g.if err := foo(); !assert.NoError(t, err) { return }) to avoid dropping the init statement and breaking compilation.requireis dot-imported (empty qualifier returned byaddImportFix), producing valid call text and fix messages.ParenExprunwrapping:collectNegatedAssertOrCallsandcollectRequireErrorNegatedOrCallsunwrap*ast.ParenExprso parenthesised conditions are recognised as fixable patterns.callIsNegatedInIfCond: replaces the earlier piecemeal pattern matching; treatsParenExprand anyBinaryExpras transparent, correctly classifying&&patterns asinIfCond(skipped, not falsely reported) while still restricting fixable patterns to||-only chains in Stage 2a.Import helper (
helpers_import.go)New file providing
addImportFix()— returns the local qualifier for a package and an optionalTextEditto add the import — ported from thehttp-constbranch to avoid merge conflicts.Registry / testgen / README
negated-assertregistered as anAdvancedChecker, enabled by default, placed betweengo-requireandrequire-error.internal/testgen/gen_negated_assert.goadded withErroredTemplateandGoldenTemplate.checkers-default/negated-assert/negated_assert_test.go+.golden.negated-assert-skip-logic/not-default test directory added, covering goroutine, HTTP handler, cleanup, compound||, andcontinuepatterns.require-error-skip-logic/negated_if_test.goupdated with// wantcomments for error assertion negated-if patterns (and.goldenfile added); compound||test uses two independent error variables to demonstrate a logically sound pattern.README.mdupdated:require-errorsection shows negated-if examples and partial autofix (🤏);negated-assertsection added; cross-references between both checkers.Closes #125