qf1006.go raw

   1  package qf1006
   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/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:     "QF1006",
  21  		Run:      run,
  22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  23  	},
  24  	Doc: &lint.RawDocumentation{
  25  		Title: `Lift \'if\'+\'break\' into loop condition`,
  26  		Before: `
  27  for {
  28      if done {
  29          break
  30      }
  31      ...
  32  }`,
  33  
  34  		After: `
  35  for !done {
  36      ...
  37  }`,
  38  		Since:    "2021.1",
  39  		Severity: lint.SeverityHint,
  40  	},
  41  })
  42  
  43  var Analyzer = SCAnalyzer.Analyzer
  44  
  45  var checkForLoopIfBreak = pattern.MustParse(`(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt "BREAK" nil) nil):_)`)
  46  
  47  func run(pass *analysis.Pass) (interface{}, error) {
  48  	fn := func(node ast.Node) {
  49  		m, ok := code.Match(pass, checkForLoopIfBreak, node)
  50  		if !ok {
  51  			return
  52  		}
  53  
  54  		pos := node.Pos() + token.Pos(len("for"))
  55  		r := astutil.NegateDeMorgan(m.State["cond"].(ast.Expr), false)
  56  
  57  		// FIXME(dh): we're leaving behind an empty line when we
  58  		// delete the old if statement. However, we can't just delete
  59  		// an additional character, in case there closing curly brace
  60  		// is followed by a comment, or Windows newlines.
  61  		report.Report(pass, m.State["if"].(ast.Node), "could lift into loop condition",
  62  			report.Fixes(edit.Fix("Lift into loop condition",
  63  				edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, r)),
  64  				edit.Delete(m.State["if"].(ast.Node)))))
  65  	}
  66  	code.Preorder(pass, fn, (*ast.ForStmt)(nil))
  67  	return nil, nil
  68  }
  69