-
Notifications
You must be signed in to change notification settings - Fork 17
Add nodurations linter #143
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
Merged
+366
−0
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
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 nodurations | ||
|
||
import ( | ||
"fmt" | ||
"go/ast" | ||
|
||
"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" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils" | ||
) | ||
|
||
const name = "nodurations" | ||
|
||
// Analyzer is the analyzer for the nodurations package. | ||
// It checks that no struct field is of a type either time.Duration or metav1.Duration. | ||
var Analyzer = &analysis.Analyzer{ | ||
Name: name, | ||
Doc: "Duration types should not be used, to avoid the need for clients to implement go duration parsing. Instead, use integer based fields with the unit in the field name.", | ||
Run: run, | ||
Requires: []*analysis.Analyzer{inspector.Analyzer}, | ||
} | ||
|
||
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, _ []ast.Node, _ extractjsontags.FieldTagInfo, markersAccess markers.Markers) { | ||
checkField(pass, field) | ||
}) | ||
|
||
inspect.InspectTypeSpec(func(typeSpec *ast.TypeSpec, markersAccess markers.Markers) { | ||
checkTypeSpec(pass, typeSpec, typeSpec, "type") | ||
}) | ||
|
||
return nil, nil //nolint:nilnil | ||
} | ||
|
||
func checkField(pass *analysis.Pass, field *ast.Field) { | ||
fieldName := utils.FieldName(field) | ||
if fieldName == "" { | ||
return | ||
} | ||
|
||
prefix := fmt.Sprintf("field %s", fieldName) | ||
|
||
checkTypeExpr(pass, field.Type, field, prefix) | ||
} | ||
|
||
//nolint:cyclop | ||
func checkTypeExpr(pass *analysis.Pass, typeExpr ast.Expr, node ast.Node, prefix string) { | ||
switch typ := typeExpr.(type) { | ||
case *ast.SelectorExpr: | ||
pkg, ok := typ.X.(*ast.Ident) | ||
if !ok { | ||
return | ||
} | ||
|
||
if typ.X == nil || (pkg.Name != "time" && pkg.Name != "metav1") { | ||
return | ||
} | ||
|
||
// Array element is not a metav1.Condition. | ||
if typ.Sel == nil || typ.Sel.Name != "Duration" { | ||
return | ||
} | ||
|
||
pass.Reportf(node.Pos(), "%s should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing.", prefix) | ||
case *ast.Ident: | ||
checkIdent(pass, typ, node, prefix) | ||
case *ast.StarExpr: | ||
checkTypeExpr(pass, typ.X, node, fmt.Sprintf("%s pointer", prefix)) | ||
case *ast.ArrayType: | ||
checkTypeExpr(pass, typ.Elt, node, fmt.Sprintf("%s array element", prefix)) | ||
case *ast.MapType: | ||
checkTypeExpr(pass, typ.Key, node, fmt.Sprintf("%s map key", prefix)) | ||
checkTypeExpr(pass, typ.Value, node, fmt.Sprintf("%s map value", prefix)) | ||
} | ||
} | ||
|
||
// checkIdent calls the checkFunc with the ident, when we have hit a built-in type. | ||
// If the ident is not a built in, we look at the underlying type until we hit a built-in type. | ||
func checkIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node, prefix string) { | ||
if utils.IsBasicType(pass, ident) { | ||
// We've hit a built-in type, no need to check further. | ||
return | ||
} | ||
|
||
tSpec, ok := utils.LookupTypeSpec(pass, ident) | ||
if !ok { | ||
return | ||
} | ||
|
||
// The field is using a type alias, check if the alias is an int. | ||
checkTypeSpec(pass, tSpec, node, fmt.Sprintf("%s type", prefix)) | ||
} | ||
|
||
func checkTypeSpec(pass *analysis.Pass, tSpec *ast.TypeSpec, node ast.Node, prefix string) { | ||
if tSpec.Name == nil { | ||
return | ||
} | ||
|
||
typeName := tSpec.Name.Name | ||
prefix = fmt.Sprintf("%s %s", prefix, typeName) | ||
|
||
checkTypeExpr(pass, tSpec.Type, node, prefix) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 nodurations_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"golang.org/x/tools/go/analysis/analysistest" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/nodurations" | ||
) | ||
|
||
func Test(t *testing.T) { | ||
testdata := analysistest.TestData() | ||
analysistest.Run(t, testdata, nodurations.Analyzer, "a") | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
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. | ||
*/ | ||
|
||
/* | ||
The `nodurations` linter checks that fields in the API types do not contain `Duration` type ether from the `time` package or the `k8s.io/apimachinery/pkg/apis/meta/v1` package. | ||
|
||
It is recommended to avoid the use of Duration types. Their use ties the API to Go's notion of duration parsing, which may be hard to implement in other languages. | ||
|
||
Instead, use an integer based field with a unit in the name, e.g. `FooSeconds`. | ||
*/ | ||
|
||
package nodurations |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
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 nodurations | ||
|
||
import ( | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/initializer" | ||
"sigs.k8s.io/kube-api-linter/pkg/analysis/registry" | ||
) | ||
|
||
func init() { | ||
registry.DefaultRegistry().RegisterLinter(Initializer()) | ||
} | ||
|
||
// Initializer returns the AnalyzerInitializer for this | ||
// Analyzer so that it can be added to the registry. | ||
func Initializer() initializer.AnalyzerInitializer { | ||
return initializer.NewInitializer( | ||
name, | ||
Analyzer, | ||
true, | ||
) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package a | ||
|
||
import ( | ||
"time" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
type Durations struct { | ||
ValidString string | ||
|
||
ValidMap map[string]string | ||
|
||
ValidInt32 int32 | ||
|
||
ValidInt64 int64 | ||
|
||
InvalidDuration time.Duration // want "field InvalidDuration should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtr *time.Duration // want "field InvalidDurationPtr pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationSlice []time.Duration // want "field InvalidDurationSlice array element should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrSlice []*time.Duration // want "field InvalidDurationPtrSlice array element pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationAlias DurationAlias // want "field InvalidDurationAlias type DurationAlias should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrAlias *DurationAlias // want "field InvalidDurationPtrAlias pointer type DurationAlias should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationSliceAlias []DurationAlias // want "field InvalidDurationSliceAlias array element type DurationAlias should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrSliceAlias []*DurationAlias // want "field InvalidDurationPtrSliceAlias array element pointer type DurationAlias should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapStringToDuration map[string]time.Duration // want "field InvalidMapStringToDuration map value should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapStringToDurationPtr map[string]*time.Duration // want "field InvalidMapStringToDurationPtr map value pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapDurationToString map[time.Duration]string // want "field InvalidMapDurationToString map key should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapDurationPtrToString map[*time.Duration]string // want "field InvalidMapDurationPtrToString map key pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationAliasFromAnotherFile DurationAliasB // want "field InvalidDurationAliasFromAnotherFile type DurationAliasB should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrAliasFromAnotherFile *DurationAliasB // want "field InvalidDurationPtrAliasFromAnotherFile pointer type DurationAliasB should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
} | ||
|
||
// DoNothing is used to check that the analyser doesn't report on methods. | ||
func (Durations) DoNothing(a bool) bool { | ||
return a | ||
} | ||
|
||
type DurationAlias time.Duration // want "type DurationAlias should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtr *time.Duration // want "type DurationAliasPtr pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasSlice []time.Duration // want "type DurationAliasSlice array element should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtrSlice []*time.Duration // want "type DurationAliasPtrSlice array element pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type MapStringToDurationAlias map[string]time.Duration // want "type MapStringToDurationAlias map value should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type MapStringToDurationPtrAlias map[string]*time.Duration // want "type MapStringToDurationPtrAlias map value pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationsWithMetaV1Package struct { | ||
ValidString string | ||
|
||
ValidMap map[string]string | ||
|
||
ValidInt32 int32 | ||
|
||
ValidInt64 int64 | ||
|
||
InvalidDuration metav1.Duration // want "field InvalidDuration should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtr *metav1.Duration // want "field InvalidDurationPtr pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationSlice []metav1.Duration // want "field InvalidDurationSlice array element should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrSlice []*metav1.Duration // want "field InvalidDurationPtrSlice array element pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationAlias DurationAliasWithMetaV1 // want "field InvalidDurationAlias type DurationAliasWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrAlias *DurationAliasWithMetaV1 // want "field InvalidDurationPtrAlias pointer type DurationAliasWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationSliceAlias []DurationAliasWithMetaV1 // want "field InvalidDurationSliceAlias array element type DurationAliasWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationPtrSliceAlias []*DurationAliasWithMetaV1 // want "field InvalidDurationPtrSliceAlias array element pointer type DurationAliasWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapStringToDuration map[string]metav1.Duration // want "field InvalidMapStringToDuration map value should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapStringToDurationPtr map[string]*metav1.Duration // want "field InvalidMapStringToDurationPtr map value pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapDurationToString map[metav1.Duration]string // want "field InvalidMapDurationToString map key should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidMapDurationPtrToString map[*metav1.Duration]string // want "field InvalidMapDurationPtrToString map key pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
InvalidDurationAliasFromAnotherFile DurationAliasBWithMetaV1 // want "field InvalidDurationAliasFromAnotherFile type DurationAliasBWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
} | ||
|
||
type DurationAliasWithMetaV1 metav1.Duration // want "type DurationAliasWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtrWithMetaV1 *metav1.Duration // want "type DurationAliasPtrWithMetaV1 pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasSliceWithMetaV1 []metav1.Duration // want "type DurationAliasSliceWithMetaV1 array element should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtrSliceWithMetaV1 []*metav1.Duration // want "type DurationAliasPtrSliceWithMetaV1 array element pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type MapStringToDurationAliaWithMetaV1 map[string]metav1.Duration // want "type MapStringToDurationAliaWithMetaV1 map value should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type MapStringToDurationPtrAliasWithMetaV1 map[string]*metav1.Duration // want "type MapStringToDurationPtrAliasWithMetaV1 map value pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package a | ||
|
||
import ( | ||
"time" | ||
|
||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
type DurationAliasB time.Duration // want "type DurationAliasB should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtrB *time.Duration // want "type DurationAliasPtrB pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasBWithMetaV1 metav1.Duration // want "type DurationAliasBWithMetaV1 should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." | ||
|
||
type DurationAliasPtrBWithMetaV1 *metav1.Duration // want "type DurationAliasPtrBWithMetaV1 pointer should not use a Duration. Use an integer type with units in the name to avoid the need for clients to implement Go style duration parsing." |
14 changes: 14 additions & 0 deletions
14
pkg/analysis/nodurations/testdata/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/* | ||
This is a copy of the minimum amount of the original file to be able to test the nodurations linter. | ||
*/ | ||
|
||
package v1 | ||
|
||
import "time" | ||
|
||
// Duration is a wrapper around time.Duration which supports correct | ||
// marshaling to YAML and JSON. In particular, it marshals into strings, which | ||
// can be used as map keys in json. | ||
type Duration struct { | ||
time.Duration `protobuf:"varint,1,opt,name=duration,casttype=time.Duration"` | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.