Skip to content

Commit 360add3

Browse files
Added noreferences linter
1 parent 7dea50a commit 360add3

File tree

13 files changed

+632
-1
lines changed

13 files changed

+632
-1
lines changed

docs/linters.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
- [Notimestamp](#notimestamp) - Prevents usage of 'TimeStamp' fields
2121
- [OptionalFields](#optionalfields) - Validates optional field conventions
2222
- [OptionalOrRequired](#optionalorrequired) - Ensures fields are explicitly marked as optional or required
23+
- [NoReferences](#noreferences) - Ensures field names use Ref/Refs instead of Reference/References
2324
- [RequiredFields](#requiredfields) - Validates required field conventions
2425
- [SSATags](#ssatags) - Ensures proper Server-Side Apply (SSA) tags on array fields
2526
- [StatusOptional](#statusoptional) - Ensures status fields are marked as optional
@@ -609,6 +610,44 @@ If you prefer not to suggest fixes for pointers in required fields, you can chan
609610
If you prefer not to suggest fixes for `omitempty` in required fields, you can change the `omitempty.policy` to `Warn` or `Ignore`.
610611
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`.
611612

613+
## NoReferences
614+
615+
The `noreferences` linter ensures that field names use 'Ref'/'Refs' instead of 'Reference'/'References'.
616+
617+
By default, `noreferences` is enabled and operates in standard mode, allowing 'Ref'/'Refs' but prohibiting 'Reference'/'References' in field names.
618+
619+
### Configuration
620+
621+
```yaml
622+
lintersConfig:
623+
noreferences:
624+
policy: PreferAbbreviatedReference | NoReferences # Defaults to `PreferAbbreviatedReference`.
625+
```
626+
627+
**Default behavior (policy: PreferAbbreviatedReference):**
628+
- Reports errors for fields containing 'Reference' or 'References' and replaces with 'Ref' or 'Refs'
629+
- **Allows** fields containing 'Ref' or 'Refs' without reporting errors
630+
631+
**Strict mode (policy: NoReferences):**
632+
- **Warns** about any reference-related words ('Ref', 'Refs', 'Reference', or 'References') in field names
633+
- Does not provide automatic fixes - serves as an informational warning
634+
- In this strict mode, the goal is to inform developers about reference-related words in field names
635+
636+
### Fixes
637+
638+
The `noreferences` linter can automatically fix field names in **PreferAbbreviatedReference mode**:
639+
640+
**PreferAbbreviatedReference mode:**
641+
- Replaces 'Reference' with 'Ref' and 'References' with 'Refs' anywhere in field names
642+
- Case insensitive matching
643+
- Examples:
644+
- `NodeReference``NodeRef`
645+
- `ReferenceNode``RefNode`
646+
- `NodeReferences``NodeRefs`
647+
648+
**Note:**
649+
- The `NoReferences` mode only reports warnings without providing fixes, allowing developers to choose appropriate field names manually.
650+
612651
## SSATags
613652

614653
The `ssatags` linter ensures that array fields in Kubernetes API objects have the appropriate
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 noreferences
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
22+
"golang.org/x/tools/go/analysis"
23+
"k8s.io/apimachinery/pkg/util/validation/field"
24+
"sigs.k8s.io/kube-api-linter/pkg/analysis/initializer"
25+
"sigs.k8s.io/kube-api-linter/pkg/analysis/namingconventions"
26+
)
27+
28+
const (
29+
name = "noreferences"
30+
doc = "Enforces that fields use Ref/Refs and not Reference/References"
31+
)
32+
33+
var (
34+
errUnexpectedInitializerType = errors.New("expected namingconventions.Initializer() to be of type initializer.ConfigurableAnalyzerInitializer, but was not")
35+
errInvalidPolicy = errors.New("invalid policy")
36+
)
37+
38+
// newAnalyzer creates a new analyzer for the noreferences linter that is a wrapper around the namingconventions linter.
39+
func newAnalyzer(cfg *Config) *analysis.Analyzer {
40+
if cfg == nil {
41+
cfg = &Config{}
42+
}
43+
44+
// Default to PreferAbbreviatedReference if no policy is specified
45+
policy := cfg.Policy
46+
if policy == "" {
47+
policy = PolicyPreferAbbreviatedReference
48+
}
49+
50+
// Build the namingconventions config based on the policy
51+
ncConfig := &namingconventions.Config{
52+
Conventions: buildConventions(policy),
53+
}
54+
55+
// Get the configurable initializer for namingconventions
56+
configInit, ok := namingconventions.Initializer().(initializer.ConfigurableAnalyzerInitializer)
57+
if !ok {
58+
panic(fmt.Errorf("getting initializer: %w", errUnexpectedInitializerType))
59+
}
60+
61+
// Validate generated namingconventions configuration
62+
errs := configInit.ValidateConfig(ncConfig, field.NewPath("noreferences"))
63+
if err := errs.ToAggregate(); err != nil {
64+
panic(fmt.Errorf("noreferences linter has an invalid namingconventions configuration: %w", err))
65+
}
66+
67+
// Initialize the wrapped analyzer
68+
analyzer, err := configInit.Init(ncConfig)
69+
if err != nil {
70+
panic(fmt.Errorf("noreferences linter encountered an error initializing wrapped namingconventions analyzer: %w", err))
71+
}
72+
73+
analyzer.Name = name
74+
analyzer.Doc = doc
75+
76+
return analyzer
77+
}
78+
79+
// buildConventions creates the naming conventions based on the policy.
80+
func buildConventions(policy Policy) []namingconventions.Convention {
81+
switch policy {
82+
case PolicyPreferAbbreviatedReference:
83+
// Replace "Reference" or "References" with "Ref" or "Refs" anywhere in field name
84+
return []namingconventions.Convention{
85+
{
86+
Name: "reference-to-ref",
87+
ViolationMatcher: "^[Rr]eference|Reference(s?)$",
88+
Operation: namingconventions.OperationReplacement,
89+
Replacement: "Ref$1",
90+
Message: "field names should use 'Ref' instead of 'Reference'",
91+
},
92+
}
93+
94+
case PolicyNoReferences:
95+
// Warn about reference words anywhere in field name without providing fixes
96+
return []namingconventions.Convention{
97+
{
98+
Name: "no-references",
99+
ViolationMatcher: "^[Rr]ef(erence)?(s?)([A-Z])|Ref(erence)?(s?)$",
100+
Operation: namingconventions.OperationInform,
101+
Message: "field names should not contain reference-related words",
102+
},
103+
}
104+
105+
default:
106+
// Should not happen due to validation
107+
panic(fmt.Errorf("%w: %s", errInvalidPolicy, policy))
108+
}
109+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 noreferences_test
17+
18+
import (
19+
"testing"
20+
21+
"golang.org/x/tools/go/analysis/analysistest"
22+
"sigs.k8s.io/kube-api-linter/pkg/analysis/noreferences"
23+
)
24+
25+
func TestPreferAbbreviatedReference(t *testing.T) {
26+
testdata := analysistest.TestData()
27+
28+
cfg := &noreferences.Config{
29+
Policy: noreferences.PolicyPreferAbbreviatedReference,
30+
}
31+
32+
analyzer, err := noreferences.Initializer().Init(cfg)
33+
if err != nil {
34+
t.Fatalf("initializing noreferences linter: %v", err)
35+
}
36+
37+
analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "a")
38+
}
39+
40+
func TestEmptyConfig(t *testing.T) {
41+
testdata := analysistest.TestData()
42+
43+
// Test with empty config - should default to PreferAbbreviatedReference behavior
44+
cfg := &noreferences.Config{}
45+
46+
analyzer, err := noreferences.Initializer().Init(cfg)
47+
if err != nil {
48+
t.Fatalf("initializing noreferences linter: %v", err)
49+
}
50+
51+
// With default config (empty Policy), it should default to PreferAbbreviatedReference behavior
52+
// So we test with folder 'a' which has the same expectations
53+
analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "a")
54+
}
55+
56+
func TestNoReferences(t *testing.T) {
57+
testdata := analysistest.TestData()
58+
59+
cfg := &noreferences.Config{
60+
Policy: noreferences.PolicyNoReferences,
61+
}
62+
63+
analyzer, err := noreferences.Initializer().Init(cfg)
64+
if err != nil {
65+
t.Fatalf("initializing noreferences linter: %v", err)
66+
}
67+
68+
analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "b")
69+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 noreferences
17+
18+
// Policy defines the policy for handling references in field names.
19+
type Policy string
20+
21+
const (
22+
// PolicyPreferAbbreviatedReference allows abbreviated forms (Ref/Refs) in field names.
23+
// It suggests replacing Reference/References with Ref/Refs.
24+
PolicyPreferAbbreviatedReference Policy = "PreferAbbreviatedReference"
25+
// PolicyNoReferences forbids any reference-related words in field names.
26+
// It suggests removing Ref/Refs/Reference/References entirely.
27+
PolicyNoReferences Policy = "NoReferences"
28+
)
29+
30+
// Config represents the configuration for the noreferences linter.
31+
type Config struct {
32+
// policy controls how reference-related words are handled in field names.
33+
// When set to PreferAbbreviatedReference (default), Reference/References are replaced with Ref/Refs.
34+
// When set to NoReferences, all reference-related words are suggested to be removed.
35+
Policy Policy `json:"policy,omitempty"`
36+
}

pkg/analysis/noreferences/doc.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 `noreferences` linter ensures that field names use 'Ref'/'Refs' instead of 'Reference'/'References'.
19+
By default, `noreferences` is enabled and enforces this naming convention.
20+
The linter checks that 'Reference' anywhere in field names (beginning, middle, or end) is replaced with 'Ref'.
21+
Similarly, 'References' anywhere in field names is replaced with 'Refs'.
22+
23+
Example configuration:
24+
Default behavior (allow Ref/Refs in field names):
25+
26+
lintersConfig:
27+
noreferences:
28+
policy: PreferAbbreviatedReference
29+
30+
Strict mode (forbid Ref/Refs in field names):
31+
32+
lintersConfig:
33+
noreferences:
34+
policy: NoReferences
35+
36+
When `policy` is set to `PreferAbbreviatedReference` (the default), fields containing 'Ref' or 'Refs' are allowed.
37+
The policy can be set to `NoReferences` to also report errors for 'Ref' or 'Refs' in field names.
38+
*/
39+
package noreferences
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 noreferences
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 noreferences linter.
45+
func validateConfig(cfg *Config, fldPath *field.Path) field.ErrorList {
46+
if cfg == nil {
47+
return nil // nil config is valid, will use defaults
48+
}
49+
50+
var errs field.ErrorList
51+
52+
// Validate Policy enum if provided
53+
switch cfg.Policy {
54+
case PolicyPreferAbbreviatedReference, PolicyNoReferences, "":
55+
default:
56+
errs = append(errs, field.NotSupported(
57+
fldPath.Child("policy"),
58+
cfg.Policy,
59+
[]string{string(PolicyPreferAbbreviatedReference), string(PolicyNoReferences)},
60+
))
61+
}
62+
return errs
63+
}

0 commit comments

Comments
 (0)