Skip to content

Commit 4e54c0c

Browse files
Added references linter
1 parent 1dea13b commit 4e54c0c

File tree

12 files changed

+458
-0
lines changed

12 files changed

+458
-0
lines changed

.golangci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ linters:
3131
- govet
3232
- importas
3333
- ineffassign
34+
- kubeapilinter
3435
- makezero
3536
- misspell
3637
- nakedret

docs/linters.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- [Notimestamp](#notimestamp) - Prevents usage of 'TimeStamp' fields
1919
- [OptionalFields](#optionalfields) - Validates optional field conventions
2020
- [OptionalOrRequired](#optionalorrequired) - Ensures fields are explicitly marked as optional or required
21+
- [References](#references) - Ensures field names use Ref/Refs instead of Reference/References
2122
- [RequiredFields](#requiredfields) - Validates required field conventions
2223
- [SSATags](#ssatags) - Ensures proper Server-Side Apply (SSA) tags on array fields
2324
- [StatusOptional](#statusoptional) - Ensures status fields are marked as optional
@@ -555,6 +556,38 @@ If you prefer not to suggest fixes for pointers in required fields, you can chan
555556
If you prefer not to suggest fixes for `omitempty` in required fields, you can change the `omitempty.policy` to `Warn` or `Ignore`.
556557
If you prefer not to suggest fixes for `omitzero` in required fields, you can change the `omitzero.policy` to `Warn` and also not to consider `omitzero` policy at all, it can be set to `Forbid`.
557558

559+
## References
560+
561+
The `references` linter ensures that field names use 'Ref'/'Refs' suffixes instead of 'Reference'/'References'.
562+
563+
By default, `references` is enabled and enforces this naming convention. The linter checks that:
564+
- Fields ending with 'Reference' are suggested to use 'Ref' instead
565+
- Fields ending with 'References' are suggested to use 'Refs' instead
566+
567+
### Configuration
568+
569+
```yaml
570+
lintersConfig:
571+
references:
572+
allowRefAndRefs: false | true # Allow Ref/Refs suffixes for OpenShift compatibility. Defaults to `false`.
573+
```
574+
575+
**Default behavior (allowRefAndRefs=false):**
576+
- Reports errors for fields ending with 'Reference' or 'References'
577+
- Also reports errors for fields ending with 'Ref' or 'Refs' (unless they were corrected from Reference/References)
578+
579+
**OpenShift compatibility (allowRefAndRefs=true):**
580+
- Reports errors for fields ending with 'Reference' or 'References'
581+
- Allows fields ending with 'Ref' or 'Refs' without reporting errors
582+
583+
### Fixes
584+
585+
The `references` linter can automatically fix field names by replacing 'Reference' with 'Ref' and 'References' with 'Refs'.
586+
587+
For example:
588+
- `NodeReference` will be suggested to be changed to `NodeRef`
589+
- `NodeReferences` will be suggested to be changed to `NodeRefs`
590+
558591
## SSATags
559592

560593
The `ssatags` linter ensures that array fields in Kubernetes API objects have the appropriate
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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+
package references
17+
18+
import (
19+
"fmt"
20+
"go/ast"
21+
"strings"
22+
23+
"golang.org/x/tools/go/analysis"
24+
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
25+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
26+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
28+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
29+
)
30+
31+
const name = "references"
32+
33+
type analyzer struct {
34+
allowRefAndRefs bool
35+
}
36+
37+
// newAnalyzer creates a new analysis.Analyzer for the references linter.
38+
func newAnalyzer(cfg *Config) *analysis.Analyzer {
39+
if cfg == nil {
40+
cfg = &Config{}
41+
}
42+
43+
a := &analyzer{
44+
allowRefAndRefs: cfg.AllowRefAndRefs,
45+
}
46+
47+
analyzer := &analysis.Analyzer{
48+
Name: name,
49+
Doc: "Enforces that fields use Ref/Refs and not Reference/References",
50+
Run: a.run,
51+
Requires: []*analysis.Analyzer{inspector.Analyzer, extractjsontags.Analyzer},
52+
}
53+
54+
return analyzer
55+
}
56+
57+
func (a *analyzer) run(pass *analysis.Pass) (any, error) {
58+
inspect, ok := pass.ResultOf[inspector.Analyzer].(inspector.Inspector)
59+
if !ok {
60+
return nil, kalerrors.ErrCouldNotGetInspector
61+
}
62+
63+
inspect.InspectFields(func(field *ast.Field, stack []ast.Node, jsonTagInfo extractjsontags.FieldTagInfo, markersAccess markers.Markers) {
64+
a.checkField(pass, field, jsonTagInfo)
65+
})
66+
67+
return nil, nil //nolint:nilnil
68+
}
69+
70+
func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, jsonTagInfo extractjsontags.FieldTagInfo) {
71+
if field == nil || len(field.Names) == 0 {
72+
return
73+
}
74+
75+
fieldName := utils.FieldName(field)
76+
77+
if strings.HasSuffix(fieldName, "Reference") {
78+
suggestedName := strings.TrimSuffix(fieldName, "Reference") + "Ref"
79+
pass.Report(analysis.Diagnostic{
80+
Pos: field.Pos(),
81+
Message: fmt.Sprintf("field %s should use 'Ref' instead of 'Reference'", fieldName),
82+
SuggestedFixes: []analysis.SuggestedFix{
83+
{
84+
Message: "replace 'Reference' with 'Ref'",
85+
TextEdits: []analysis.TextEdit{
86+
{
87+
Pos: field.Names[0].Pos(),
88+
NewText: []byte(suggestedName),
89+
End: field.Names[0].End(),
90+
},
91+
},
92+
},
93+
},
94+
})
95+
return
96+
}
97+
98+
if strings.HasSuffix(fieldName, "References") {
99+
suggestedName := strings.TrimSuffix(fieldName, "References") + "Refs"
100+
pass.Report(analysis.Diagnostic{
101+
Pos: field.Pos(),
102+
Message: fmt.Sprintf("field %s should use 'Refs' instead of 'References'", fieldName),
103+
SuggestedFixes: []analysis.SuggestedFix{
104+
{
105+
Message: "replace 'References' with 'Refs'",
106+
TextEdits: []analysis.TextEdit{
107+
{
108+
Pos: field.Names[0].Pos(),
109+
NewText: []byte(suggestedName),
110+
End: field.Names[0].End(),
111+
},
112+
},
113+
},
114+
},
115+
})
116+
return
117+
}
118+
119+
// If allowRefAndRefs is false, report errors for standalone Ref/Refs fields
120+
// If allowRefAndRefs is true (OpenShift), don't report errors for Ref/Refs fields
121+
if !a.allowRefAndRefs {
122+
// Check for fields ending with Ref or Refs (excluding those already handled above)
123+
if fieldName != "Ref" && strings.HasSuffix(fieldName, "Ref") && !strings.HasSuffix(fieldName, "eRef") {
124+
pass.Reportf(field.Pos(), "field %s should not use 'Ref' suffix", fieldName)
125+
}
126+
127+
if fieldName != "Refs" && strings.HasSuffix(fieldName, "Refs") && !strings.HasSuffix(fieldName, "eRefs") {
128+
pass.Reportf(field.Pos(), "field %s should not use 'Refs' suffix", fieldName)
129+
}
130+
}
131+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
package references_test
17+
18+
import (
19+
"testing"
20+
21+
"golang.org/x/tools/go/analysis/analysistest"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/references"
23+
)
24+
25+
func TestDefaultConfiguration(t *testing.T) {
26+
testdata := analysistest.TestData()
27+
28+
cfg := &references.Config{
29+
AllowRefAndRefs: true,
30+
}
31+
32+
analyzer, err := references.Initializer().Init(cfg)
33+
if err != nil {
34+
t.Fatalf("initializing references linter: %v", err)
35+
}
36+
37+
analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "a")
38+
}

pkg/analysis/references/config.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+
package references
17+
18+
// Config represents the configuration for the references linter.
19+
type Config struct {
20+
// AllowRefAndRefs, when set to true, allows fields ending with "Ref" or "Refs".
21+
// This is useful for OpenShift compatibility where Ref/Refs fields are used instead of Reference/References.
22+
// When false (default), the linter will report errors for all fields ending with "Ref" or "Refs".
23+
AllowRefAndRefs bool `json:"allowRefAndRefs,omitempty"`
24+
}

pkg/analysis/references/doc.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
The `references` linter ensures that field names use 'Ref'/'Refs' suffixes and not 'Reference'/'References'.
19+
20+
By default, `references` is enabled and enforces this naming convention.
21+
22+
The linter checks that fields ending with 'Reference' are suggested to use 'Ref' instead.
23+
Similarly, fields ending with 'References' are suggested to use 'Refs' instead.
24+
25+
Example configuration:
26+
27+
**Default behavior (report errors for Reference/References):**
28+
```yaml
29+
lintersConfig:
30+
31+
references: {}
32+
33+
```
34+
35+
**For OpenShift compatibility (allow Ref/Refs):**
36+
```yaml
37+
lintersConfig:
38+
39+
references:
40+
allowRefAndRefs: true
41+
42+
```
43+
44+
When `allowRefAndRefs` is set to false (the default), fields ending with 'Ref' or 'Refs' (other than those matched by the above rules) will also be reported as errors.
45+
This is useful to ensure consistency across the codebase. However, for OpenShift compatibility, this option can be set to true to allow such field names.
46+
*/
47+
package references
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
package references
17+
18+
import (
19+
"golang.org/x/tools/go/analysis"
20+
"k8s.io/apimachinery/pkg/util/validation/field"
21+
"sigs.k8s.io/kube-api-linter/pkg/analysis/initializer"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/registry"
23+
)
24+
25+
func init() {
26+
registry.DefaultRegistry().RegisterLinter(Initializer())
27+
}
28+
29+
// Initializer returns the AnalyzerInitializer for this
30+
// Analyzer so that it can be added to the registry.
31+
func Initializer() initializer.AnalyzerInitializer {
32+
return initializer.NewConfigurableInitializer(
33+
name,
34+
initAnalyzer,
35+
true,
36+
validateConfig,
37+
)
38+
}
39+
40+
func initAnalyzer(cfg *Config) (*analysis.Analyzer, error) {
41+
return newAnalyzer(cfg), nil
42+
}
43+
44+
// validateConfig validates the configuration for the references linter.
45+
func validateConfig(cfg *Config, fldPath *field.Path) field.ErrorList {
46+
// No validation needed for this simple config
47+
return nil
48+
}

0 commit comments

Comments
 (0)