github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/quickfix/qf1006/qf1006.go (about)

     1  package qf1006
     2  
     3  import (
     4  	"go/ast"
     5  	"go/token"
     6  
     7  	"github.com/amarpal/go-tools/analysis/code"
     8  	"github.com/amarpal/go-tools/analysis/edit"
     9  	"github.com/amarpal/go-tools/analysis/lint"
    10  	"github.com/amarpal/go-tools/analysis/report"
    11  	"github.com/amarpal/go-tools/go/ast/astutil"
    12  	"github.com/amarpal/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.Documentation{
    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  }