s1018.go raw
1 package s1018
2
3 import (
4 "go/ast"
5
6 "honnef.co/go/tools/analysis/code"
7 "honnef.co/go/tools/analysis/edit"
8 "honnef.co/go/tools/analysis/facts/generated"
9 "honnef.co/go/tools/analysis/lint"
10 "honnef.co/go/tools/analysis/report"
11 "honnef.co/go/tools/go/types/typeutil"
12 "honnef.co/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.RawDocumentation{
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 }
85