src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

qf1001.go (3523B)


      1 package qf1001
      2 
      3 import (
      4 	"go/ast"
      5 	"go/types"
      6 
      7 	"honnef.co/go/tools/analysis/code"
      8 	"honnef.co/go/tools/analysis/edit"
      9 	"honnef.co/go/tools/analysis/lint"
     10 	"honnef.co/go/tools/analysis/report"
     11 	"honnef.co/go/tools/go/ast/astutil"
     12 	"honnef.co/go/tools/pattern"
     13 
     14 	"golang.org/x/tools/go/analysis"
     15 	"golang.org/x/tools/go/analysis/passes/inspect"
     16 )
     17 
     18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     19 	Analyzer: &analysis.Analyzer{
     20 		Name:     "QF1001",
     21 		Run:      CheckDeMorgan,
     22 		Requires: []*analysis.Analyzer{inspect.Analyzer},
     23 	},
     24 	Doc: &lint.RawDocumentation{
     25 		Title:    "Apply De Morgan's law",
     26 		Since:    "2021.1",
     27 		Severity: lint.SeverityHint,
     28 	},
     29 })
     30 
     31 var Analyzer = SCAnalyzer.Analyzer
     32 
     33 var demorganQ = pattern.MustParse(`(UnaryExpr "!" expr@(BinaryExpr _ _ _))`)
     34 
     35 func CheckDeMorgan(pass *analysis.Pass) (any, error) {
     36 	// TODO(dh): support going in the other direction, e.g. turning `!a && !b && !c` into `!(a || b || c)`
     37 
     38 	// hasFloats reports whether any subexpression is of type float.
     39 	hasFloats := func(expr ast.Expr) bool {
     40 		found := false
     41 		ast.Inspect(expr, func(node ast.Node) bool {
     42 			if expr, ok := node.(ast.Expr); ok {
     43 				if typ := pass.TypesInfo.TypeOf(expr); typ != nil {
     44 					if basic, ok := typ.Underlying().(*types.Basic); ok {
     45 						if (basic.Info() & types.IsFloat) != 0 {
     46 							found = true
     47 							return false
     48 						}
     49 					}
     50 				}
     51 			}
     52 			return true
     53 		})
     54 		return found
     55 	}
     56 
     57 	for c := range code.Cursor(pass).Preorder((*ast.UnaryExpr)(nil)) {
     58 		node := c.Node()
     59 		matcher, ok := code.Match(pass, demorganQ, node)
     60 		if !ok {
     61 			continue
     62 		}
     63 
     64 		expr := matcher.State["expr"].(ast.Expr)
     65 
     66 		// be extremely conservative when it comes to floats
     67 		if hasFloats(expr) {
     68 			continue
     69 		}
     70 
     71 		n := astutil.NegateDeMorgan(expr, false)
     72 		nr := astutil.NegateDeMorgan(expr, true)
     73 		nc, ok := astutil.CopyExpr(n)
     74 		if !ok {
     75 			continue
     76 		}
     77 		ns := astutil.SimplifyParentheses(nc)
     78 		nrc, ok := astutil.CopyExpr(nr)
     79 		if !ok {
     80 			continue
     81 		}
     82 		nrs := astutil.SimplifyParentheses(nrc)
     83 
     84 		var bn, bnr, bns, bnrs string
     85 		switch c.Parent().Node().(type) {
     86 		case *ast.BinaryExpr, *ast.IfStmt, *ast.ForStmt, *ast.SwitchStmt:
     87 			// Always add parentheses for if, for and switch. If
     88 			// they're unnecessary, go/printer will strip them when
     89 			// the whole file gets formatted.
     90 
     91 			bn = report.Render(pass, &ast.ParenExpr{X: n})
     92 			bnr = report.Render(pass, &ast.ParenExpr{X: nr})
     93 			bns = report.Render(pass, &ast.ParenExpr{X: ns})
     94 			bnrs = report.Render(pass, &ast.ParenExpr{X: nrs})
     95 
     96 		default:
     97 			// TODO are there other types where we don't want to strip parentheses?
     98 			bn = report.Render(pass, n)
     99 			bnr = report.Render(pass, nr)
    100 			bns = report.Render(pass, ns)
    101 			bnrs = report.Render(pass, nrs)
    102 		}
    103 
    104 		// Note: we cannot compare the ASTs directly, because
    105 		// simplifyParentheses might have rebalanced trees without
    106 		// affecting the rendered form.
    107 		var fixes []analysis.SuggestedFix
    108 		fixes = append(fixes, edit.Fix("Apply De Morgan's law", edit.ReplaceWithString(node, bn)))
    109 		if bn != bns {
    110 			fixes = append(fixes, edit.Fix("Apply De Morgan's law & simplify", edit.ReplaceWithString(node, bns)))
    111 		}
    112 		if bn != bnr {
    113 			fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively", edit.ReplaceWithString(node, bnr)))
    114 			if bnr != bnrs {
    115 				fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively & simplify", edit.ReplaceWithString(node, bnrs)))
    116 			}
    117 		}
    118 
    119 		report.Report(pass, node, "could apply De Morgan's law", report.Fixes(fixes...))
    120 	}
    121 
    122 	return nil, nil
    123 }