s1021.go raw

   1  package s1021
   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/facts/generated"
  10  	"honnef.co/go/tools/analysis/lint"
  11  	"honnef.co/go/tools/analysis/report"
  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:     "S1021",
  20  		Run:      run,
  21  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
  22  	},
  23  	Doc: &lint.RawDocumentation{
  24  		Title: `Merge variable declaration and assignment`,
  25  		Before: `
  26  var x uint
  27  x = 1`,
  28  		After:   `var x uint = 1`,
  29  		Since:   "2017.1",
  30  		MergeIf: lint.MergeIfAny,
  31  	},
  32  })
  33  
  34  var Analyzer = SCAnalyzer.Analyzer
  35  
  36  func run(pass *analysis.Pass) (interface{}, error) {
  37  	hasMultipleAssignments := func(root ast.Node, ident *ast.Ident) bool {
  38  		num := 0
  39  		ast.Inspect(root, func(node ast.Node) bool {
  40  			if num >= 2 {
  41  				return false
  42  			}
  43  			assign, ok := node.(*ast.AssignStmt)
  44  			if !ok {
  45  				return true
  46  			}
  47  			for _, lhs := range assign.Lhs {
  48  				if oident, ok := lhs.(*ast.Ident); ok {
  49  					if pass.TypesInfo.ObjectOf(oident) == pass.TypesInfo.ObjectOf(ident) {
  50  						num++
  51  					}
  52  				}
  53  			}
  54  
  55  			return true
  56  		})
  57  		return num >= 2
  58  	}
  59  	fn := func(node ast.Node) {
  60  		block := node.(*ast.BlockStmt)
  61  		if len(block.List) < 2 {
  62  			return
  63  		}
  64  		for i, stmt := range block.List[:len(block.List)-1] {
  65  			_ = i
  66  			decl, ok := stmt.(*ast.DeclStmt)
  67  			if !ok {
  68  				continue
  69  			}
  70  			gdecl, ok := decl.Decl.(*ast.GenDecl)
  71  			if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 {
  72  				continue
  73  			}
  74  			vspec, ok := gdecl.Specs[0].(*ast.ValueSpec)
  75  			if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 {
  76  				continue
  77  			}
  78  
  79  			assign, ok := block.List[i+1].(*ast.AssignStmt)
  80  			if !ok || assign.Tok != token.ASSIGN {
  81  				continue
  82  			}
  83  			if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
  84  				continue
  85  			}
  86  			ident, ok := assign.Lhs[0].(*ast.Ident)
  87  			if !ok {
  88  				continue
  89  			}
  90  			if pass.TypesInfo.ObjectOf(vspec.Names[0]) != pass.TypesInfo.ObjectOf(ident) {
  91  				continue
  92  			}
  93  
  94  			if code.RefersTo(pass, assign.Rhs[0], pass.TypesInfo.ObjectOf(ident)) {
  95  				continue
  96  			}
  97  			if hasMultipleAssignments(block, ident) {
  98  				continue
  99  			}
 100  
 101  			r := &ast.GenDecl{
 102  				Specs: []ast.Spec{
 103  					&ast.ValueSpec{
 104  						Names:  vspec.Names,
 105  						Values: []ast.Expr{assign.Rhs[0]},
 106  						Type:   vspec.Type,
 107  					},
 108  				},
 109  				Tok: gdecl.Tok,
 110  			}
 111  			report.Report(pass, decl, "should merge variable declaration with assignment on next line",
 112  				report.FilterGenerated(),
 113  				report.Fixes(edit.Fix("merge declaration with assignment", edit.ReplaceWithNode(pass.Fset, edit.Range{decl.Pos(), assign.End()}, r))))
 114  		}
 115  	}
 116  	code.Preorder(pass, fn, (*ast.BlockStmt)(nil))
 117  	return nil, nil
 118  }
 119