github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1018/s1018.go (about) 1 package s1018 2 3 import ( 4 "go/ast" 5 6 "github.com/amarpal/go-tools/analysis/code" 7 "github.com/amarpal/go-tools/analysis/edit" 8 "github.com/amarpal/go-tools/analysis/facts/generated" 9 "github.com/amarpal/go-tools/analysis/lint" 10 "github.com/amarpal/go-tools/analysis/report" 11 "github.com/amarpal/go-tools/go/types/typeutil" 12 "github.com/amarpal/go-tools/pattern" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 ) 17 18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 19 Analyzer: &analysis.Analyzer{ 20 Name: "S1018", 21 Run: run, 22 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 23 }, 24 Doc: &lint.Documentation{ 25 Title: `Use \"copy\" for sliding elements`, 26 Text: `\'copy()\' permits using the same source and destination slice, even with 27 overlapping ranges. This makes it ideal for sliding elements in a 28 slice.`, 29 30 Before: ` 31 for i := 0; i < n; i++ { 32 bs[i] = bs[offset+i] 33 }`, 34 After: `copy(bs[:n], bs[offset:])`, 35 Since: "2017.1", 36 MergeIf: lint.MergeIfAny, 37 }, 38 }) 39 40 var Analyzer = SCAnalyzer.Analyzer 41 42 var ( 43 checkLoopSlideQ = pattern.MustParse(` 44 (ForStmt 45 (AssignStmt initvar@(Ident _) _ (IntegerLiteral "0")) 46 (BinaryExpr initvar "<" limit@(Ident _)) 47 (IncDecStmt initvar "++") 48 [(AssignStmt 49 (IndexExpr slice@(Ident _) initvar) 50 "=" 51 (IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`) 52 checkLoopSlideR = pattern.MustParse(` 53 (CallExpr 54 (Ident "copy") 55 [(SliceExpr slice nil limit nil) 56 (SliceExpr slice offset nil nil)])`) 57 ) 58 59 func run(pass *analysis.Pass) (interface{}, error) { 60 // TODO(dh): detect bs[i+offset] in addition to bs[offset+i] 61 // TODO(dh): consider merging this function with LintLoopCopy 62 // TODO(dh): detect length that is an expression, not a variable name 63 // TODO(dh): support sliding to a different offset than the beginning of the slice 64 65 fn := func(node ast.Node) { 66 loop := node.(*ast.ForStmt) 67 m, edits, ok := code.MatchAndEdit(pass, checkLoopSlideQ, checkLoopSlideR, loop) 68 if !ok { 69 return 70 } 71 typ := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident)) 72 // The pattern probably needs a core type, but All is fine, too. Either way we only accept slices. 73 if !typeutil.All(typ, typeutil.IsSlice) { 74 return 75 } 76 77 report.Report(pass, loop, "should use copy() instead of loop for sliding slice elements", 78 report.ShortRange(), 79 report.FilterGenerated(), 80 report.Fixes(edit.Fix("use copy() instead of loop", edits...))) 81 } 82 code.Preorder(pass, fn, (*ast.ForStmt)(nil)) 83 return nil, nil 84 }