-
Notifications
You must be signed in to change notification settings - Fork 14
Add notimestamp linter #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
Copyright 2025 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package notimestamp | ||
|
||
import ( | ||
"fmt" | ||
"go/ast" | ||
"go/token" | ||
"regexp" | ||
"strings" | ||
|
||
"golang.org/x/tools/go/analysis" | ||
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector" | ||
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils" | ||
) | ||
|
||
const name = "notimestamp" | ||
|
||
// Analyzer is the analyzer for the notimestamp package. | ||
// It checks that no struct fields named 'timestamp', or that contain timestamp as a | ||
// substring are present. | ||
var Analyzer = &analysis.Analyzer{ | ||
Name: name, | ||
Doc: "check that none of the struct field named timestamp or contain timestamp as a substring", | ||
Run: run, | ||
Requires: []*analysis.Analyzer{inspector.Analyzer}, | ||
} | ||
|
||
// case insensitive regular expression to match 'timestamp' string in field or json tag. | ||
var timeStampRegEx = regexp.MustCompile("(?i)timestamp") | ||
|
||
func run(pass *analysis.Pass) (any, error) { | ||
inspect, ok := pass.ResultOf[inspector.Analyzer].(inspector.Inspector) | ||
if !ok { | ||
return nil, kalerrors.ErrCouldNotGetInspector | ||
} | ||
|
||
inspect.InspectFields(func(field *ast.Field, stack []ast.Node, jsonTagInfo extractjsontags.FieldTagInfo, markersAccess markershelper.Markers) { | ||
checkFieldsAndTags(pass, field, jsonTagInfo) | ||
}) | ||
|
||
return nil, nil //nolint:nilnil | ||
} | ||
|
||
func checkFieldsAndTags(pass *analysis.Pass, field *ast.Field, tagInfo extractjsontags.FieldTagInfo) { | ||
fieldName := utils.FieldName(field) | ||
if fieldName == "" { | ||
return | ||
} | ||
|
||
var suggestedFixes []analysis.SuggestedFix | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixes should be explained in the linters documentation, see how others have done that |
||
|
||
// check if filed name contains timestamp in it. | ||
fieldReplacementName := timeStampRegEx.ReplaceAllString(fieldName, "Time") | ||
if fieldReplacementName != fieldName { | ||
suggestedFixes = append(suggestedFixes, analysis.SuggestedFix{ | ||
Message: "replace field with Time", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these normally have to be unique, have you tried this against a file that has multiple failures using a full golangci-lint run? |
||
TextEdits: []analysis.TextEdit{ | ||
{ | ||
Pos: field.Pos(), | ||
NewText: []byte(fieldReplacementName), | ||
End: field.Pos() + token.Pos(len(fieldName)), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
// check if the tag contains timestamp in it. | ||
tagReplacementName := timeStampRegEx.ReplaceAllString(tagInfo.Name, "Time") | ||
if strings.HasPrefix(strings.ToLower(tagInfo.Name), "time") { | ||
// If tag is starts with 'timeStamp', the replacement should be 'time' not 'Time'. | ||
tagReplacementName = timeStampRegEx.ReplaceAllString(tagInfo.Name, "time") | ||
} | ||
|
||
if tagReplacementName != tagInfo.Name { | ||
suggestedFixes = append(suggestedFixes, analysis.SuggestedFix{ | ||
Message: "replace json tag with Time", | ||
TextEdits: []analysis.TextEdit{ | ||
{ | ||
Pos: tagInfo.Pos, | ||
NewText: []byte(tagReplacementName), | ||
End: tagInfo.Pos + token.Pos(len(tagInfo.Name)), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
if len(suggestedFixes) > 0 { | ||
pass.Report(analysis.Diagnostic{ | ||
Pos: field.Pos(), | ||
Message: fmt.Sprintf("field %s: prefer use of the term time over timestamp", fieldName), | ||
SuggestedFixes: suggestedFixes, | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
Copyright 2025 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package notimestamp_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"golang.org/x/tools/go/analysis/analysistest" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/notimestamp" | ||
) | ||
|
||
func Test(t *testing.T) { | ||
testdata := analysistest.TestData() | ||
analysistest.RunWithSuggestedFixes(t, testdata, notimestamp.Analyzer, "a") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
Copyright 2025 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
/* | ||
notimestamp provides a linter to ensure that structs do not contain a TimeStamp field. | ||
|
||
The linter will flag any struct field containing the substring 'timestamp'. This means both | ||
TimeStamp and FooTimeStamp will be flagged. | ||
*/ | ||
|
||
package notimestamp |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
Copyright 2025 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package notimestamp | ||
|
||
import ( | ||
"golang.org/x/tools/go/analysis" | ||
"sigs.k8s.io/kube-api-linter/pkg/config" | ||
) | ||
|
||
// Initializer returns the AnalyzerInitializer for this | ||
// Analyzer so that it can be added to the registry. | ||
func Initializer() initializer { | ||
return initializer{} | ||
} | ||
|
||
// intializer implements the AnalyzerInitializer interface. | ||
type initializer struct{} | ||
|
||
// Name returns the name of the Analyzer. | ||
func (initializer) Name() string { | ||
return name | ||
} | ||
|
||
// Init returns the intialized Analyzer. | ||
func (initializer) Init(cfg config.LintersConfig) (*analysis.Analyzer, error) { | ||
return Analyzer, nil | ||
} | ||
|
||
// Default determines whether this Analyzer is on by default, or not. | ||
func (initializer) Default() bool { | ||
return true | ||
} | ||
Comment on lines
+24
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A recent refactor has changed the way functions init, can you please rebase and look at another, non-configurable linter to see how this should look now.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure will do that |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package a | ||
|
||
import "time" | ||
|
||
type NoTimeStampTestStruct struct { | ||
// +optional | ||
TimeStamp *time.Time `json:"timeStamp,omitempty"` // want "field TimeStamp: prefer use of the term time over timestamp" | ||
} | ||
|
||
// DoNothing is used to check that the analyser doesn't report on methods. | ||
func (NoTimeStampTestStruct) DoNothing() {} | ||
|
||
type NoSubTimeStampTestStruct struct { | ||
// +optional | ||
FooTimeStamp *time.Time `json:"fooTimeStamp,omitempty"` // want "field FooTimeStamp: prefer use of the term time over timestamp" | ||
} | ||
|
||
type SerializedTimeStampTestStruct struct { | ||
// +optional | ||
FooTime *time.Time `json:"fooTime,omitempty"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package a | ||
|
||
import "time" | ||
|
||
type NoTimeStampTestStruct struct { | ||
// +optional | ||
Time *time.Time `json:"time,omitempty"` // want "field TimeStamp: prefer use of the term time over timestamp" | ||
} | ||
|
||
// DoNothing is used to check that the analyser doesn't report on methods. | ||
func (NoTimeStampTestStruct) DoNothing() {} | ||
|
||
type NoSubTimeStampTestStruct struct { | ||
// +optional | ||
FooTime *time.Time `json:"fooTime,omitempty"` // want "field FooTimeStamp: prefer use of the term time over timestamp" | ||
} | ||
|
||
type SerializedTimeStampTestStruct struct { | ||
// +optional | ||
FooTime *time.Time `json:"fooTime,omitempty"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ import ( | |
"sigs.k8s.io/kube-api-linter/pkg/analysis/nofloats" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/nomaps" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/nophase" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/notimestamp" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalfields" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/optionalorrequired" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/requiredfields" | ||
|
@@ -86,6 +87,7 @@ func NewRegistry() Registry { | |
nofloats.Initializer(), | ||
nomaps.Initializer(), | ||
nophase.Initializer(), | ||
notimestamp.Initializer(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will conflict when you rebase, we don't maintain this list anymore as each linter now self registers by blank importing in a package called |
||
optionalfields.Initializer(), | ||
optionalorrequired.Initializer(), | ||
requiredfields.Initializer(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.