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

     1  package s1011
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"go/types"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/edit"
    11  	"github.com/amarpal/go-tools/analysis/facts/generated"
    12  	"github.com/amarpal/go-tools/analysis/facts/purity"
    13  	"github.com/amarpal/go-tools/analysis/lint"
    14  	"github.com/amarpal/go-tools/analysis/report"
    15  	"github.com/amarpal/go-tools/pattern"
    16  
    17  	"golang.org/x/tools/go/analysis"
    18  	"golang.org/x/tools/go/analysis/passes/inspect"
    19  )
    20  
    21  var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
    22  	Analyzer: &analysis.Analyzer{
    23  		Name:     "S1011",
    24  		Run:      run,
    25  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, purity.Analyzer},
    26  	},
    27  	Doc: &lint.Documentation{
    28  		Title: `Use a single \'append\' to concatenate two slices`,
    29  		Before: `
    30  for _, e := range y {
    31      x = append(x, e)
    32  }
    33  
    34  for i := range y {
    35      x = append(x, y[i])
    36  }
    37  
    38  for i := range y {
    39      v := y[i]
    40      x = append(x, v)
    41  }`,
    42  
    43  		After: `
    44  x = append(x, y...)
    45  x = append(x, y...)
    46  x = append(x, y...)`,
    47  		Since: "2017.1",
    48  		// MergeIfAll because y might not be a slice under all build tags.
    49  		MergeIf: lint.MergeIfAll,
    50  	},
    51  })
    52  
    53  var Analyzer = SCAnalyzer.Analyzer
    54  
    55  var checkLoopAppendQ = pattern.MustParse(`
    56  (Or
    57  	(RangeStmt
    58  		(Ident "_")
    59  		val@(Object _)
    60  		_
    61  		x
    62  		[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])])
    63  	(RangeStmt
    64  		idx@(Object _)
    65  		nil
    66  		_
    67  		x
    68  		[(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])])
    69  	(RangeStmt
    70  		idx@(Object _)
    71  		nil
    72  		_
    73  		x
    74  		[(AssignStmt val@(Object _) ":=" (IndexExpr x idx))
    75  		(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]))`)
    76  
    77  func run(pass *analysis.Pass) (interface{}, error) {
    78  	pure := pass.ResultOf[purity.Analyzer].(purity.Result)
    79  
    80  	fn := func(node ast.Node) {
    81  		m, ok := code.Match(pass, checkLoopAppendQ, node)
    82  		if !ok {
    83  			return
    84  		}
    85  
    86  		if val, ok := m.State["val"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), val) {
    87  			return
    88  		}
    89  
    90  		if m.State["idx"] != nil && code.MayHaveSideEffects(pass, m.State["x"].(ast.Expr), pure) {
    91  			// When using an index-based loop, x gets evaluated repeatedly and thus should be pure.
    92  			// This doesn't matter for value-based loops, because x only gets evaluated once.
    93  			return
    94  		}
    95  
    96  		if idx, ok := m.State["idx"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), idx) {
    97  			// The lhs mustn't refer to the index loop variable.
    98  			return
    99  		}
   100  
   101  		if code.MayHaveSideEffects(pass, m.State["lhs"].(ast.Expr), pure) {
   102  			// The lhs may be dynamic and return different values on each iteration. For example:
   103  			//
   104  			// 	func bar() map[int][]int { /* return one of several maps */ }
   105  			//
   106  			// 	func foo(x []int, y [][]int) {
   107  			// 		for i := range x {
   108  			// 			bar()[0] = append(bar()[0], x[i])
   109  			// 		}
   110  			// 	}
   111  			//
   112  			// The dynamic nature of the lhs might also affect the value of the index.
   113  			return
   114  		}
   115  
   116  		src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr))
   117  		dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr))
   118  		if !types.Identical(src, dst) {
   119  			return
   120  		}
   121  
   122  		r := &ast.AssignStmt{
   123  			Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)},
   124  			Tok: token.ASSIGN,
   125  			Rhs: []ast.Expr{
   126  				&ast.CallExpr{
   127  					Fun: &ast.Ident{Name: "append"},
   128  					Args: []ast.Expr{
   129  						m.State["lhs"].(ast.Expr),
   130  						m.State["x"].(ast.Expr),
   131  					},
   132  					Ellipsis: 1,
   133  				},
   134  			},
   135  		}
   136  
   137  		report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)),
   138  			report.ShortRange(),
   139  			report.FilterGenerated(),
   140  			report.Fixes(edit.Fix("replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r))))
   141  	}
   142  	code.Preorder(pass, fn, (*ast.RangeStmt)(nil))
   143  	return nil, nil
   144  }