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