Skip to content

Commit cece271

Browse files
everettravenJoelSpeed
authored andcommitted
(feature): add nofloats linter
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 304c215 commit cece271

File tree

8 files changed

+218
-0
lines changed

8 files changed

+218
-0
lines changed

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ The `nobools` linter checks that fields in the API types do not contain a `bool`
222222
Booleans are limited and do not evolve well over time.
223223
It is recommended instead to create a string alias with meaningful values, as an enum.
224224

225+
## NoFloats
226+
227+
The `nofloats` linter checks that fields in the API types do not contain a `float32` or `float64` type.
228+
229+
Floating-point values cannot be reliably round-tripped without changing and have varying precision and representation across languages and architectures.
230+
Their use should be avoided as much as possible.
231+
They should never be used in spec.
232+
225233
## Nophase
226234

227235
The `nophase` linter checks that the fields in the API types don't contain a 'Phase', or any field which contains 'Phase' as a substring, e.g MachinePhase.

pkg/analysis/nofloats/analyzer.go

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package nofloats
2+
3+
import (
4+
"errors"
5+
"go/ast"
6+
7+
"github.com/JoelSpeed/kal/pkg/analysis/utils"
8+
"golang.org/x/tools/go/analysis"
9+
"golang.org/x/tools/go/analysis/passes/inspect"
10+
"golang.org/x/tools/go/ast/inspector"
11+
)
12+
13+
const name = "nofloats"
14+
15+
var (
16+
errCouldNotGetInspector = errors.New("could not get inspector")
17+
)
18+
19+
// Analyzer is the analyzer for the nofloats package.
20+
// It checks that no struct fields are `float`.
21+
var Analyzer = &analysis.Analyzer{
22+
Name: name,
23+
Doc: "Float values cannot be reliably round-tripped without changing and have varying precisions and representations across languages and architectures.",
24+
Run: run,
25+
Requires: []*analysis.Analyzer{inspect.Analyzer},
26+
}
27+
28+
func run(pass *analysis.Pass) (interface{}, error) {
29+
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
30+
if !ok {
31+
return nil, errCouldNotGetInspector
32+
}
33+
34+
// Filter to structs so that we can look at fields within structs.
35+
// Filter typespecs so that we can look at type aliases.
36+
nodeFilter := []ast.Node{
37+
(*ast.StructType)(nil),
38+
(*ast.TypeSpec)(nil),
39+
}
40+
41+
typeChecker := utils.NewTypeChecker(checkFloat)
42+
43+
// Preorder visits all the nodes of the AST in depth-first order. It calls
44+
// f(n) for each node n before it visits n's children.
45+
//
46+
// We use the filter defined above, ensuring we only look at struct fields and type declarations.
47+
inspect.Preorder(nodeFilter, func(n ast.Node) {
48+
typeChecker.CheckNode(pass, n)
49+
})
50+
51+
return nil, nil //nolint:nilnil
52+
}
53+
54+
func checkFloat(pass *analysis.Pass, ident *ast.Ident, node ast.Node, prefix string) {
55+
if ident.Name == "float32" || ident.Name == "float64" {
56+
pass.Reportf(node.Pos(), "%s should not use a float value because they cannot be reliably round-tripped.", prefix)
57+
}
58+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package nofloats_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/JoelSpeed/kal/pkg/analysis/nofloats"
7+
"golang.org/x/tools/go/analysis/analysistest"
8+
)
9+
10+
func Test(t *testing.T) {
11+
testdata := analysistest.TestData()
12+
analysistest.Run(t, testdata, nofloats.Analyzer, "a")
13+
}

pkg/analysis/nofloats/doc.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
nofloats is an analyzer that ensures fields in the API types do not contain a `float32` or `float64` type.
3+
4+
Floating-point values cannot be reliably round-tripped without changing
5+
and have varying precision and representation across languages and architectures.
6+
Their use should be avoided as much as possible.
7+
They should never be used in spec.
8+
*/
9+
package nofloats

pkg/analysis/nofloats/initializer.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package nofloats
2+
3+
import (
4+
"github.com/JoelSpeed/kal/pkg/config"
5+
"golang.org/x/tools/go/analysis"
6+
)
7+
8+
// Initializer returns the AnalyzerInitializer for this
9+
// Analyzer so that it can be added to the registry.
10+
func Initializer() initializer {
11+
return initializer{}
12+
}
13+
14+
// intializer implements the AnalyzerInitializer interface.
15+
type initializer struct{}
16+
17+
// Name returns the name of the Analyzer.
18+
func (initializer) Name() string {
19+
return name
20+
}
21+
22+
// Init returns the intialized Analyzer.
23+
func (initializer) Init(cfg config.LintersConfig) (*analysis.Analyzer, error) {
24+
return Analyzer, nil
25+
}
26+
27+
// Default determines whether this Analyzer is on by default, or not.
28+
func (initializer) Default() bool {
29+
// Floats avoidance in the Kube conventions is a must for spec fields.
30+
// Doesn't hurt to enforce it widely, uses outside of spec fields should be
31+
// evaluated on an individual basis to determine if it is reasonable to override.
32+
return true
33+
}
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package a
2+
3+
type Floats struct {
4+
ValidString string
5+
6+
ValidMap map[string]string
7+
8+
ValidInt32 int32
9+
10+
ValidInt64 int64
11+
12+
InvalidFloat32 float32 // want "field InvalidFloat32 should not use a float value because they cannot be reliably round-tripped."
13+
14+
InvalidFloat64 float64 // want "field InvalidFloat64 should not use a float value because they cannot be reliably round-tripped."
15+
16+
InvalidFloat32Ptr *float32 // want "field InvalidFloat32Ptr pointer should not use a float value because they cannot be reliably round-tripped."
17+
18+
InvalidFloat64Ptr *float64 // want "field InvalidFloat64Ptr pointer should not use a float value because they cannot be reliably round-tripped."
19+
20+
InvalidFloat32Slice []float32 // want "field InvalidFloat32Slice array element should not use a float value because they cannot be reliably round-tripped."
21+
22+
InvalidFloat64Slice []float64 // want "field InvalidFloat64Slice array element should not use a float value because they cannot be reliably round-tripped."
23+
24+
InvalidFloat32PtrSlice []*float32 // want "field InvalidFloat32PtrSlice array element pointer should not use a float value because they cannot be reliably round-tripped."
25+
26+
InvalidFloat64PtrSlice []*float64 // want "field InvalidFloat64PtrSlice array element pointer should not use a float value because they cannot be reliably round-tripped."
27+
28+
InvalidFloat32Alias Float32Alias // want "field InvalidFloat32Alias type Float32Alias should not use a float value because they cannot be reliably round-tripped."
29+
30+
InvalidFloat64Alias Float64Alias // want "field InvalidFloat64Alias type Float64Alias should not use a float value because they cannot be reliably round-tripped."
31+
32+
InvalidFloat32PtrAlias *Float32Alias // want "field InvalidFloat32PtrAlias pointer type Float32Alias should not use a float value because they cannot be reliably round-tripped."
33+
34+
InvalidFloat64PtrAlias *Float64Alias // want "field InvalidFloat64PtrAlias pointer type Float64Alias should not use a float value because they cannot be reliably round-tripped."
35+
36+
InvalidFloat32SliceAlias []Float32Alias // want "field InvalidFloat32SliceAlias array element type Float32Alias should not use a float value because they cannot be reliably round-tripped."
37+
38+
InvalidFloat64SliceAlias []Float64Alias // want "field InvalidFloat64SliceAlias array element type Float64Alias should not use a float value because they cannot be reliably round-tripped."
39+
40+
InvalidFloat32PtrSliceAlias []*Float32Alias // want "field InvalidFloat32PtrSliceAlias array element pointer type Float32Alias should not use a float value because they cannot be reliably round-tripped."
41+
42+
InvalidFloat64PtrSliceAlias []*Float64Alias // want "field InvalidFloat64PtrSliceAlias array element pointer type Float64Alias should not use a float value because they cannot be reliably round-tripped."
43+
44+
InvalidMapStringToFloat32 map[string]float32 // want "field InvalidMapStringToFloat32 map value should not use a float value because they cannot be reliably round-tripped."
45+
46+
InvalidMapStringToFloat64 map[string]float64 // want "field InvalidMapStringToFloat64 map value should not use a float value because they cannot be reliably round-tripped."
47+
48+
InvalidMapStringToFloat32Ptr map[string]*float32 // want "field InvalidMapStringToFloat32Ptr map value pointer should not use a float value because they cannot be reliably round-tripped."
49+
50+
InvalidMapStringToFloat64Ptr map[string]*float64 // want "field InvalidMapStringToFloat64Ptr map value pointer should not use a float value because they cannot be reliably round-tripped."
51+
52+
InvalidMapFloat32ToString map[float32]string // want "field InvalidMapFloat32ToString map key should not use a float value because they cannot be reliably round-tripped."
53+
54+
InvalidMapFloat64ToString map[float64]string // want "field InvalidMapFloat64ToString map key should not use a float value because they cannot be reliably round-tripped."
55+
56+
InvalidMapFloat32PtrToString map[*float32]string // want "field InvalidMapFloat32PtrToString map key pointer should not use a float value because they cannot be reliably round-tripped."
57+
58+
InvalidMapFloat64PtrToString map[*float64]string // want "field InvalidMapFloat64PtrToString map key pointer should not use a float value because they cannot be reliably round-tripped."
59+
}
60+
61+
// DoNothingFloat32 is used to check that the analyser doesn't report on methods.
62+
func (Floats) DoNothingFloat32(a float32) float32 {
63+
return a
64+
}
65+
66+
// DoNothingFloat64 is used to check that the analyser doesn't report on methods.
67+
func (Floats) DoNothingFloat64(a float64) float64 {
68+
return a
69+
}
70+
71+
type Float32Alias float32 // want "type Float32Alias should not use a float value because they cannot be reliably round-tripped."
72+
73+
type Float64Alias float64 // want "type Float64Alias should not use a float value because they cannot be reliably round-tripped."
74+
75+
type Float32AliasPtr *float32 // want "type Float32AliasPtr pointer should not use a float value because they cannot be reliably round-tripped."
76+
77+
type Float64AliasPtr *float64 // want "type Float64AliasPtr pointer should not use a float value because they cannot be reliably round-tripped."
78+
79+
type Float32AliasSlice []float32 // want "type Float32AliasSlice array element should not use a float value because they cannot be reliably round-tripped."
80+
81+
type Float64AliasSlice []float64 // want "type Float64AliasSlice array element should not use a float value because they cannot be reliably round-tripped."
82+
83+
type Float32AliasPtrSlice []*float32 // want "type Float32AliasPtrSlice array element pointer should not use a float value because they cannot be reliably round-tripped."
84+
85+
type Float64AliasPtrSlice []*float64 // want "type Float64AliasPtrSlice array element pointer should not use a float value because they cannot be reliably round-tripped."
86+
87+
type MapStringToFloat32Alias map[string]float32 // want "type MapStringToFloat32Alias map value should not use a float value because they cannot be reliably round-tripped."
88+
89+
type MapStringToFloat64Alias map[string]float64 // want "type MapStringToFloat64Alias map value should not use a float value because they cannot be reliably round-tripped."
90+
91+
type MapStringToFloat32PtrAlias map[string]*float32 // want "type MapStringToFloat32PtrAlias map value pointer should not use a float value because they cannot be reliably round-tripped."
92+
93+
type MapStringToFloat64PtrAlias map[string]*float64 // want "type MapStringToFloat64PtrAlias map value pointer should not use a float value because they cannot be reliably round-tripped."

pkg/analysis/registry.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/JoelSpeed/kal/pkg/analysis/jsontags"
1010
"github.com/JoelSpeed/kal/pkg/analysis/maxlength"
1111
"github.com/JoelSpeed/kal/pkg/analysis/nobools"
12+
"github.com/JoelSpeed/kal/pkg/analysis/nofloats"
1213
"github.com/JoelSpeed/kal/pkg/analysis/nophase"
1314
"github.com/JoelSpeed/kal/pkg/analysis/optionalorrequired"
1415
"github.com/JoelSpeed/kal/pkg/analysis/requiredfields"
@@ -61,6 +62,7 @@ func NewRegistry() Registry {
6162
jsontags.Initializer(),
6263
maxlength.Initializer(),
6364
nobools.Initializer(),
65+
nofloats.Initializer(),
6466
nophase.Initializer(),
6567
optionalorrequired.Initializer(),
6668
requiredfields.Initializer(),

pkg/analysis/registry_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var _ = Describe("Registry", func() {
2020
"commentstart",
2121
"integers",
2222
"jsontags",
23+
"nofloats",
2324
"nophase",
2425
"optionalorrequired",
2526
"requiredfields",
@@ -37,6 +38,7 @@ var _ = Describe("Registry", func() {
3738
"jsontags",
3839
"maxlength",
3940
"nobools",
41+
"nofloats",
4042
"nophase",
4143
"optionalorrequired",
4244
"requiredfields",

0 commit comments

Comments
 (0)