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

     1  package qf1007
     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/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.Documentation{
    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  }