-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanalyzer.go
104 lines (87 loc) · 2.39 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
// Copyright: (c) 2022, Mathias Weber ([email protected])
package floatcompare
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/printer"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/analysis"
)
type floatcompare struct {
equalOnly bool
skipTests bool
}
// NewAnalyzer create a new analyzer for float compare
func NewAnalyzer() *analysis.Analyzer {
fc := floatcompare{}
var flagSet flag.FlagSet
flagSet.BoolVar(&fc.equalOnly, "equalOnly", false, "should the linter only search for == and !=")
flagSet.BoolVar(&fc.skipTests, "skipTests", false, "should the linter execute on test files as well")
return &analysis.Analyzer{
Name: "floatcompare",
Doc: "Search for float comparison, since these are potential errors",
Run: fc.run,
Flags: flagSet,
}
}
func (fc *floatcompare) isCheckExpr(node ast.Node, pass *analysis.Pass) {
switch expr := node.(type) {
case *ast.BinaryExpr:
fc.checkBinExpr(expr, pass)
case *ast.SwitchStmt:
if fc.isFloat(expr.Tag, pass) {
pass.Reportf(expr.Tag.Pos(), "float comparison with switch statement")
return
}
}
}
func (fc *floatcompare) isFloat(expr ast.Expr, pass *analysis.Pass) bool {
t := pass.TypesInfo.TypeOf(expr)
if t == nil {
return false
}
bt, ok := t.Underlying().(*types.Basic)
if !ok {
return false
}
if (bt.Info() & types.IsFloat) == 0 {
return false
}
return true
}
func (fc *floatcompare) checkBinExpr(binExpr *ast.BinaryExpr, pass *analysis.Pass) {
if fc.equalOnly && !(binExpr.Op == token.EQL || binExpr.Op == token.NEQ) {
return
}
if !(binExpr.Op == token.EQL || binExpr.Op == token.LEQ || binExpr.Op == token.LSS || binExpr.Op == token.GEQ || binExpr.Op == token.GTR || binExpr.Op == token.NEQ) {
return
}
if !fc.isFloat(binExpr.X, pass) || !fc.isFloat(binExpr.Y, pass) {
return
}
pass.Reportf(binExpr.Pos(), "float comparison found %q",
render(pass.Fset, binExpr))
}
func (fc *floatcompare) run(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
if fc.skipTests && strings.HasSuffix(pass.Fset.Position(f.Pos()).Filename, "_test.go") {
continue
}
ast.Inspect(f, func(node ast.Node) bool {
fc.isCheckExpr(node, pass)
return true
})
}
return nil, nil
}
func render(fset *token.FileSet, x interface{}) string {
var buf bytes.Buffer
if err := printer.Fprint(&buf, fset, x); err != nil {
return fmt.Sprintf("ERROR during token parsing: %v", err)
}
return buf.String()
}