-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathanalyzer.go
144 lines (118 loc) · 3.91 KB
/
analyzer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package mirror
import (
"flag"
"go/ast"
"strings"
"github.com/butuzov/mirror/internal/checker"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
func NewAnalyzer() *analysis.Analyzer {
flags := flags()
return &analysis.Analyzer{
Name: "mirror",
Doc: "reports wrong mirror patterns of bytes/strings usage",
Run: run,
Requires: []*analysis.Analyzer{
inspect.Analyzer,
},
Flags: flags,
}
}
func run(pass *analysis.Pass) (interface{}, error) {
withTests := pass.Analyzer.Flags.Lookup("with-tests").Value.String() == "true"
// --- Reporting violations via issues ---------------------------------------
for _, violation := range Run(pass, withTests) {
pass.Report(violation.Diagnostic(pass.Fset))
}
return nil, nil
}
func Run(pass *analysis.Pass, withTests bool) []*checker.Violation {
violations := []*checker.Violation{}
// --- Setup -----------------------------------------------------------------
check := checker.New(
BytesFunctions, BytesBufferMethods,
RegexpFunctions, RegexpRegexpMethods,
StringFunctions, StringsBuilderMethods,
BufioMethods, HTTPTestMethods,
OsFileMethods, MaphashMethods,
UTF8Functions,
)
check.Type = checker.WrapType(pass.TypesInfo)
check.Print = checker.WrapPrint(pass.Fset)
ins, _ := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
imports := checker.Load(pass.Fset, ins)
// --- Preorder Checker ------------------------------------------------------
ins.Preorder([]ast.Node{(*ast.CallExpr)(nil)}, func(n ast.Node) {
callExpr := n.(*ast.CallExpr)
fileName := pass.Fset.Position(callExpr.Pos()).Filename
if !withTests && strings.HasSuffix(fileName, "_test.go") {
return
}
// -------------------------------------------------------------------------
switch expr := callExpr.Fun.(type) {
// NOTE(butuzov): Regular calls (`*ast.SelectorExpr`) like strings.HasPrefix
// or re.Match are handled by this check
case *ast.SelectorExpr:
x, ok := expr.X.(*ast.Ident)
if !ok {
return
}
// TODO(butuzov): Add check for the ast.ParenExpr in e.Fun so we can
// target the constructions like this (and other calls)
// -----------------------------------------------------------------------
// Example:
// (&maphash.Hash{}).Write([]byte("foobar"))
// -----------------------------------------------------------------------
// Case 1: Is this is a function call?
pkgName, name := x.Name, expr.Sel.Name
if pkg, ok := imports.Lookup(fileName, pkgName); ok {
if v := check.Match(pkg, name); v != nil {
if args, found := check.Handle(v, callExpr); found {
violations = append(violations, v.With(check.Print(expr.X), callExpr, args))
}
return
}
}
// Case 2: Is this is a method call?
tv := pass.TypesInfo.Types[expr.X]
if !tv.IsValue() || tv.Type == nil {
return
}
pkgStruct, name := cleanAsterisk(tv.Type.String()), expr.Sel.Name
for _, v := range check.Matches(pkgStruct, name) {
if v == nil {
continue
}
if args, found := check.Handle(v, callExpr); found {
violations = append(violations, v.With(check.Print(expr.X), callExpr, args))
return
}
}
case *ast.Ident:
// NOTE(butuzov): Special case of "." imported packages, only functions.
if pkg, ok := imports.Lookup(fileName, "."); ok {
if v := check.Match(pkg, expr.Name); v != nil {
if args, found := check.Handle(v, callExpr); found {
violations = append(violations, v.With(nil, callExpr, args))
}
return
}
}
}
})
return violations
}
func flags() flag.FlagSet {
set := flag.NewFlagSet("", flag.PanicOnError)
set.Bool("with-tests", false, "do not skip tests in reports")
set.Bool("with-debug", false, "debug linter run (development only)")
return *set
}
func cleanAsterisk(s string) string {
if strings.HasPrefix(s, "*") {
return s[1:]
}
return s
}