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

     1  package s1021
     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/facts/generated"
    10  	"github.com/amarpal/go-tools/analysis/lint"
    11  	"github.com/amarpal/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.Documentation{
    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  }