Skip to content

Commit 9957ad0

Browse files
committed
Add notimestamp linter
1 parent bce00ef commit 9957ad0

File tree

8 files changed

+228
-1
lines changed

8 files changed

+228
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,12 @@ lintersConfig:
302302

303303
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.
304304

305-
## OptionalFields
305+
## Notimestamp
306+
307+
The `notimestamp` linter checks that the fields in the API types don't contain a 'Timestamp', or any field which contains 'Timestamp' as a substring, e.g CreateTimestamp.
308+
309+
310+
## OptionalFields
306311

307312
The `optionalfields` linter checks that all fields marked as optional adhere to being pointers and having the `omitempty` value in their `json` tag where appropriate.
308313

pkg/analysis/notimestamp/analyzer.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package notimestamp
18+
19+
import (
20+
"go/ast"
21+
"strings"
22+
23+
"golang.org/x/tools/go/analysis"
24+
"golang.org/x/tools/go/analysis/passes/inspect"
25+
"golang.org/x/tools/go/ast/inspector"
26+
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
28+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
29+
)
30+
31+
const name = "notimestamp"
32+
33+
// Analyzer is the analyzer for the notimestamp package.
34+
// It checks that no struct fields named 'timestamp', or that contain timestamp as a
35+
// substring are present.
36+
var Analyzer = &analysis.Analyzer{
37+
Name: name,
38+
Doc: "check that non of the struct field named timestamp or contain timestamp as a substring",
39+
Run: run,
40+
Requires: []*analysis.Analyzer{inspect.Analyzer, extractjsontags.Analyzer},
41+
}
42+
43+
func run(pass *analysis.Pass) (any, error) {
44+
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
45+
if !ok {
46+
return nil, kalerrors.ErrCouldNotGetInspector
47+
}
48+
49+
jsonTags, ok := pass.ResultOf[extractjsontags.Analyzer].(extractjsontags.StructFieldTags)
50+
if !ok {
51+
return nil, kalerrors.ErrCouldNotGetJSONTags
52+
}
53+
54+
// Filter to fields so that we can iterate over fields in a struct.
55+
nodeFilter := []ast.Node{
56+
(*ast.Field)(nil),
57+
}
58+
59+
// Preorder visits all the nodes of the AST in depth-first order. It calls
60+
// f(n) for each node n before it visits n's children.
61+
//
62+
// We use the filter defined above, ensuring we only look at struct fields.
63+
inspect.Preorder(nodeFilter, func(n ast.Node) {
64+
field, ok := n.(*ast.Field)
65+
if !ok {
66+
return
67+
}
68+
69+
if field == nil || len(field.Names) == 0 {
70+
return
71+
}
72+
73+
fieldName := utils.FieldName(field)
74+
75+
// First check if the struct field name contains 'timestamp'
76+
if strings.Contains(strings.ToLower(fieldName), "timestamp") {
77+
pass.Reportf(field.Pos(),
78+
"field %s: fields with timestamp substring should be avoided",
79+
fieldName,
80+
)
81+
82+
return
83+
}
84+
85+
// Then check if the json serialization of the field contains 'timestamp'
86+
tagInfo := jsonTags.FieldTags(field)
87+
88+
if strings.Contains(strings.ToLower(tagInfo.Name), "timestamp") {
89+
pass.Reportf(field.Pos(),
90+
"field %s: fields with timestamp substring should be avoided",
91+
fieldName,
92+
)
93+
}
94+
})
95+
96+
return nil, nil //nolint:nilnil
97+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package notimestamp_test
18+
19+
import (
20+
"sigs.k8s.io/kube-api-linter/pkg/analysis/notimestamp"
21+
"testing"
22+
23+
"golang.org/x/tools/go/analysis/analysistest"
24+
)
25+
26+
func Test(t *testing.T) {
27+
testdata := analysistest.TestData()
28+
analysistest.RunWithSuggestedFixes(t, testdata, notimestamp.Analyzer, "a")
29+
}

pkg/analysis/notimestamp/doc.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*
18+
notimestamp provides a linter to ensure that structs do not contain a TimeStamp field.
19+
20+
The linter will flag any struct field containing the substring 'timestamp'. This means both
21+
TimeStamp and FooTimeStamp will be flagged.
22+
*/
23+
24+
package notimestamp
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package notimestamp
18+
19+
import (
20+
"golang.org/x/tools/go/analysis"
21+
"sigs.k8s.io/kube-api-linter/pkg/config"
22+
)
23+
24+
// Initializer returns the AnalyzerInitializer for this
25+
// Analyzer so that it can be added to the registry.
26+
func Initializer() initializer {
27+
return initializer{}
28+
}
29+
30+
// intializer implements the AnalyzerInitializer interface.
31+
type initializer struct{}
32+
33+
// Name returns the name of the Analyzer.
34+
func (initializer) Name() string {
35+
return name
36+
}
37+
38+
// Init returns the intialized Analyzer.
39+
func (initializer) Init(cfg config.LintersConfig) (*analysis.Analyzer, error) {
40+
return Analyzer, nil
41+
}
42+
43+
// Default determines whether this Analyzer is on by default, or not.
44+
func (initializer) Default() bool {
45+
return true
46+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package a
2+
3+
import "time"
4+
5+
type NoTimeStampTestStruct struct {
6+
// +optional
7+
TimeStamp *time.Time `json:"timeStamp,omitempty"` // want "field TimeStamp: fields with timestamp substring should be avoided"
8+
}
9+
10+
// DoNothing is used to check that the analyser doesn't report on methods.
11+
func (NoTimeStampTestStruct) DoNothing() {}
12+
13+
type NoSubTimeStampTestStruct struct {
14+
// +optional
15+
FooTimeStamp *time.Time `json:"fooTimeStamp,omitempty"` // want "field FooTimeStamp: fields with timestamp substring should be avoided"
16+
17+
}
18+
19+
type SerializedTimeStampTestStruct struct {
20+
// +optional
21+
FooTime *time.Time `json:"fooTime,omitempty"`
22+
}

pkg/analysis/registry.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sigs.k8s.io/kube-api-linter/pkg/analysis/nofloats"
3030
"sigs.k8s.io/kube-api-linter/pkg/analysis/nomaps"
3131
"sigs.k8s.io/kube-api-linter/pkg/analysis/nophase"
32+
"sigs.k8s.io/kube-api-linter/pkg/analysis/notimestamp"
3233
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalfields"
3334
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalorrequired"
3435
"sigs.k8s.io/kube-api-linter/pkg/analysis/requiredfields"
@@ -86,6 +87,7 @@ func NewRegistry() Registry {
8687
nofloats.Initializer(),
8788
nomaps.Initializer(),
8889
nophase.Initializer(),
90+
notimestamp.Initializer(),
8991
optionalfields.Initializer(),
9092
optionalorrequired.Initializer(),
9193
requiredfields.Initializer(),

pkg/analysis/registry_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var _ = Describe("Registry", func() {
3939
"nofloats",
4040
"nomaps",
4141
"nophase",
42+
"notimestamp",
4243
"optionalfields",
4344
"optionalorrequired",
4445
"requiredfields",
@@ -62,6 +63,7 @@ var _ = Describe("Registry", func() {
6263
"nofloats",
6364
"nomaps",
6465
"nophase",
66+
"notimestamp",
6567
"optionalfields",
6668
"optionalorrequired",
6769
"requiredfields",

0 commit comments

Comments
 (0)