qf1007.go raw

   1  package qf1007
   2  
   3  import (
   4  	"go/ast"
   5  	"go/token"
   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/pattern"
  12  
  13  	"golang.org/x/tools/go/analysis"
  14  	"golang.org/x/tools/go/analysis/passes/inspect"
  15  )
  16  
  17  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
  18  	Analyzer: &analysis.Analyzer{
  19  		Name:     "QF1007",
  20  		Run:      run,
  21  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  22  	},
  23  	Doc: &lint.RawDocumentation{
  24  		Title: "Merge conditional assignment into variable declaration",
  25  		Before: `
  26  x := false
  27  if someCondition {
  28      x = true
  29  }`,
  30  		After:    `x := someCondition`,
  31  		Since:    "2021.1",
  32  		Severity: lint.SeverityHint,
  33  	},
  34  })
  35  
  36  var Analyzer = SCAnalyzer.Analyzer
  37  
  38  var checkConditionalAssignmentQ = pattern.MustParse(`(AssignStmt x@(Object _) ":=" assign@(Builtin b@(Or "true" "false")))`)
  39  var checkConditionalAssignmentIfQ = pattern.MustParse(`(IfStmt nil cond [(AssignStmt x@(Object _) "=" (Builtin b@(Or "true" "false")))] nil)`)
  40  
  41  func run(pass *analysis.Pass) (interface{}, error) {
  42  	fn := func(node ast.Node) {
  43  		var body *ast.BlockStmt
  44  		switch node := node.(type) {
  45  		case *ast.FuncDecl:
  46  			body = node.Body
  47  		case *ast.FuncLit:
  48  			body = node.Body
  49  		default:
  50  			panic("unreachable")
  51  		}
  52  		if body == nil {
  53  			return
  54  		}
  55  
  56  		stmts := body.List
  57  		if len(stmts) < 2 {
  58  			return
  59  		}
  60  		for i, first := range stmts[:len(stmts)-1] {
  61  			second := stmts[i+1]
  62  			m1, ok := code.Match(pass, checkConditionalAssignmentQ, first)
  63  			if !ok {
  64  				continue
  65  			}
  66  			m2, ok := code.Match(pass, checkConditionalAssignmentIfQ, second)
  67  			if !ok {
  68  				continue
  69  			}
  70  			if m1.State["x"] != m2.State["x"] {
  71  				continue
  72  			}
  73  			if m1.State["b"] == m2.State["b"] {
  74  				continue
  75  			}
  76  
  77  			v := m2.State["cond"].(ast.Expr)
  78  			if m1.State["b"] == "true" {
  79  				v = &ast.UnaryExpr{
  80  					Op: token.NOT,
  81  					X:  v,
  82  				}
  83  			}
  84  			report.Report(pass, first, "could merge conditional assignment into variable declaration",
  85  				report.Fixes(edit.Fix("Merge conditional assignment into variable declaration",
  86  					edit.ReplaceWithNode(pass.Fset, m1.State["assign"].(ast.Node), v),
  87  					edit.Delete(second))))
  88  		}
  89  	}
  90  	code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil))
  91  	return nil, nil
  92  }
  93