Files
odidere/vendor/honnef.co/go/tools/quickfix/qf1001/qf1001.go
2026-06-06 01:17:29 +00:00

124 lines
3.4 KiB
Go

package qf1001
import (
"go/ast"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/go/ast/astutil"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "QF1001",
Run: CheckDeMorgan,
Requires: []*analysis.Analyzer{inspect.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: "Apply De Morgan's law",
Since: "2021.1",
Severity: lint.SeverityHint,
},
})
var Analyzer = SCAnalyzer.Analyzer
var demorganQ = pattern.MustParse(`(UnaryExpr "!" expr@(BinaryExpr _ _ _))`)
func CheckDeMorgan(pass *analysis.Pass) (any, error) {
// TODO(dh): support going in the other direction, e.g. turning `!a && !b && !c` into `!(a || b || c)`
// hasFloats reports whether any subexpression is of type float.
hasFloats := func(expr ast.Expr) bool {
found := false
ast.Inspect(expr, func(node ast.Node) bool {
if expr, ok := node.(ast.Expr); ok {
if typ := pass.TypesInfo.TypeOf(expr); typ != nil {
if basic, ok := typ.Underlying().(*types.Basic); ok {
if (basic.Info() & types.IsFloat) != 0 {
found = true
return false
}
}
}
}
return true
})
return found
}
for c := range code.Cursor(pass).Preorder((*ast.UnaryExpr)(nil)) {
node := c.Node()
matcher, ok := code.Match(pass, demorganQ, node)
if !ok {
continue
}
expr := matcher.State["expr"].(ast.Expr)
// be extremely conservative when it comes to floats
if hasFloats(expr) {
continue
}
n := astutil.NegateDeMorgan(expr, false)
nr := astutil.NegateDeMorgan(expr, true)
nc, ok := astutil.CopyExpr(n)
if !ok {
continue
}
ns := astutil.SimplifyParentheses(nc)
nrc, ok := astutil.CopyExpr(nr)
if !ok {
continue
}
nrs := astutil.SimplifyParentheses(nrc)
var bn, bnr, bns, bnrs string
switch c.Parent().Node().(type) {
case *ast.BinaryExpr, *ast.IfStmt, *ast.ForStmt, *ast.SwitchStmt:
// Always add parentheses for if, for and switch. If
// they're unnecessary, go/printer will strip them when
// the whole file gets formatted.
bn = report.Render(pass, &ast.ParenExpr{X: n})
bnr = report.Render(pass, &ast.ParenExpr{X: nr})
bns = report.Render(pass, &ast.ParenExpr{X: ns})
bnrs = report.Render(pass, &ast.ParenExpr{X: nrs})
default:
// TODO are there other types where we don't want to strip parentheses?
bn = report.Render(pass, n)
bnr = report.Render(pass, nr)
bns = report.Render(pass, ns)
bnrs = report.Render(pass, nrs)
}
// Note: we cannot compare the ASTs directly, because
// simplifyParentheses might have rebalanced trees without
// affecting the rendered form.
var fixes []analysis.SuggestedFix
fixes = append(fixes, edit.Fix("Apply De Morgan's law", edit.ReplaceWithString(node, bn)))
if bn != bns {
fixes = append(fixes, edit.Fix("Apply De Morgan's law & simplify", edit.ReplaceWithString(node, bns)))
}
if bn != bnr {
fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively", edit.ReplaceWithString(node, bnr)))
if bnr != bnrs {
fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively & simplify", edit.ReplaceWithString(node, bnrs)))
}
}
report.Report(pass, node, "could apply De Morgan's law", report.Fixes(fixes...))
}
return nil, nil
}