-
Notifications
You must be signed in to change notification settings - Fork 21
Add references linter #163
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 2 commits
4e54c0c
4807f4b
7f06ecd
2502e96
c5a8a96
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 |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| - [Notimestamp](#notimestamp) - Prevents usage of 'TimeStamp' fields | ||
| - [OptionalFields](#optionalfields) - Validates optional field conventions | ||
| - [OptionalOrRequired](#optionalorrequired) - Ensures fields are explicitly marked as optional or required | ||
| - [References](#references) - Ensures field names use Ref/Refs instead of Reference/References | ||
| - [RequiredFields](#requiredfields) - Validates required field conventions | ||
| - [SSATags](#ssatags) - Ensures proper Server-Side Apply (SSA) tags on array fields | ||
| - [StatusOptional](#statusoptional) - Ensures status fields are marked as optional | ||
|
|
@@ -555,6 +556,41 @@ If you prefer not to suggest fixes for pointers in required fields, you can chan | |
| If you prefer not to suggest fixes for `omitempty` in required fields, you can change the `omitempty.policy` to `Warn` or `Ignore`. | ||
| 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`. | ||
|
|
||
| ## References | ||
|
|
||
| The `references` linter ensures that field names use 'Ref'/'Refs' instead of 'Reference'/'References'. | ||
|
|
||
| By default, `references` is enabled and operates in strict mode, prohibiting the use of 'Reference', 'References', 'Ref', or 'Refs' anywhere in field names | ||
|
||
|
|
||
| ### Configuration | ||
|
|
||
| ```yaml | ||
| lintersConfig: | ||
| references: | ||
| policy: ForbidRefAndRefs | AllowRefAndRefs # Defaults to `ForbidRefAndRefs`. | ||
| ``` | ||
|
|
||
| **Default behavior (policy: ForbidRefAndRefs):** | ||
| - Reports errors for fields containing 'Reference' or 'References' anywhere and suggests replacing with 'Ref' or 'Refs' | ||
|
||
| - **Also reports errors** for fields containing 'Ref' or 'Refs' anywhere (these should be dropped/removed entirely) | ||
| - In this strict mode, the goal is to avoid all Ref/Refs/Reference/References in field names | ||
|
|
||
| **Allow Ref/Refs (policy: AllowRefAndRefs):** | ||
| - Reports errors for fields containing 'Reference' or 'References' and suggests replacing with 'Ref' or 'Refs' | ||
| - **Allows** fields containing 'Ref' or 'Refs' without reporting errors | ||
|
|
||
| ### Fixes | ||
|
|
||
| The `references` linter can automatically fix field names by replacing 'Reference' with 'Ref' and 'References' with 'Refs' anywhere in the field name. | ||
|
|
||
| For example: | ||
| - `NodeReference` → `NodeRef` | ||
| - `ReferenceNode` → `RefNode` | ||
| - `NodeReferenceConfig` → `NodeRefConfig` | ||
| - `NodeReferences` → `NodeRefs` | ||
|
|
||
| Note: In the default `ForbidRefAndRefs` mode, these fixes are intermediate - the linter will still report errors on the resulting 'Ref'/'Refs' patterns, indicating they should be removed entirely | ||
|
|
||
| ## SSATags | ||
|
|
||
| The `ssatags` linter ensures that array fields in Kubernetes API objects have the appropriate | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,9 +36,11 @@ type analyzer struct { | |
| conventions []Convention | ||
| } | ||
|
|
||
| // newAnalyzer creates a new analysis.Analyzer for the namingconventions | ||
| // NewAnalyzer creates a new analysis.Analyzer for the namingconventions | ||
| // linter based on the provided config. | ||
| func newAnalyzer(cfg *Config) *analysis.Analyzer { | ||
| // This function is exported to allow other linters to wrap namingconventions | ||
| // with fixed configuration. | ||
| func NewAnalyzer(cfg *Config) *analysis.Analyzer { | ||
|
||
| a := &analyzer{ | ||
| conventions: cfg.Conventions, | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| /* | ||
| 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 references | ||
|
|
||
| import ( | ||
| "golang.org/x/tools/go/analysis" | ||
| "sigs.k8s.io/kube-api-linter/pkg/analysis/namingconventions" | ||
| ) | ||
|
|
||
| const name = "references" | ||
|
|
||
| // newAnalyzer creates a new analysis.Analyzer for the references linter. | ||
| // The references linter is implemented as a wrapper around the namingconventions | ||
| // linter with fixed configuration based on the policy. | ||
| func newAnalyzer(cfg *Config) *analysis.Analyzer { | ||
| if cfg == nil { | ||
| cfg = &Config{} | ||
| } | ||
|
|
||
| // Default to ForbidRefAndRefs if no policy is specified | ||
| policy := cfg.Policy | ||
| if policy == "" { | ||
| policy = PolicyForbidRefAndRefs | ||
| } | ||
|
|
||
| // Build naming conventions based on policy | ||
| conventions := buildConventions(policy) | ||
|
|
||
| // Create namingconventions config | ||
| ncConfig := &namingconventions.Config{ | ||
| Conventions: conventions, | ||
| } | ||
|
|
||
| // Create the underlying namingconventions analyzer | ||
| ncAnalyzer := namingconventions.NewAnalyzer(ncConfig) | ||
|
|
||
| // Wrap it to return our name | ||
| analyzer := &analysis.Analyzer{ | ||
| Name: name, | ||
| Doc: "Enforces that fields use Ref/Refs and not Reference/References", | ||
| Run: ncAnalyzer.Run, | ||
| Requires: ncAnalyzer.Requires, | ||
| } | ||
|
|
||
| return analyzer | ||
| } | ||
|
|
||
| // buildConventions creates the naming conventions based on the policy | ||
| func buildConventions(policy Policy) []namingconventions.Convention { | ||
|
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. Try making this a switch on the policy, and returning a single convention from each branch of the switch, I think that will make the code more logical and the UX better |
||
| conventions := []namingconventions.Convention{ | ||
| // Match "References" anywhere (case-sensitive, capital R) | ||
| { | ||
| Name: "references-to-refs", | ||
| ViolationMatcher: "References", | ||
| Operation: namingconventions.OperationReplacement, | ||
| Replacement: "Refs", | ||
| Message: "field names should use 'Refs' instead of 'References'", | ||
| }, | ||
| // Match "Reference" but not when part of "References" | ||
| // Match Reference followed by: end-of-string OR non-'s' character | ||
| { | ||
| Name: "reference-to-ref", | ||
| ViolationMatcher: "Reference([^s]|$)", | ||
| Operation: namingconventions.OperationReplacement, | ||
| Replacement: "Ref$1", | ||
| Message: "field names should use 'Ref' instead of 'Reference'", | ||
| }, | ||
| } | ||
|
|
||
| // If policy is ForbidRefAndRefs, add conventions to forbid Ref/Refs anywhere | ||
| // Exclude patterns already handled by Reference/References above | ||
| if policy == PolicyForbidRefAndRefs { | ||
|
||
| conventions = append(conventions, | ||
|
||
| // Match "Refs" but not when part of "References" | ||
| namingconventions.Convention{ | ||
| Name: "forbid-refs", | ||
| ViolationMatcher: "Refs([^a-z]|$)", | ||
|
||
| Operation: namingconventions.OperationInform, | ||
|
||
| Message: "should not use 'Refs'", | ||
| }, | ||
| // Match "Ref" but not when part of "Reference", "References", or "Refs" | ||
| namingconventions.Convention{ | ||
| Name: "forbid-ref", | ||
| ViolationMatcher: "Ref([^ers]|$)", | ||
| Operation: namingconventions.OperationInform, | ||
| Message: "should not use 'Ref'", | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| return conventions | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| /* | ||
| 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 references_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "golang.org/x/tools/go/analysis/analysistest" | ||
| "sigs.k8s.io/kube-api-linter/pkg/analysis/references" | ||
| ) | ||
|
|
||
| func TestAllowRefAndRefs(t *testing.T) { | ||
| testdata := analysistest.TestData() | ||
|
|
||
| cfg := &references.Config{ | ||
| Policy: references.PolicyAllowRefAndRefs, | ||
| } | ||
|
|
||
| analyzer, err := references.Initializer().Init(cfg) | ||
| if err != nil { | ||
| t.Fatalf("initializing references linter: %v", err) | ||
| } | ||
|
|
||
| analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "a") | ||
| } | ||
|
|
||
| func TestEmptyConfig(t *testing.T) { | ||
| testdata := analysistest.TestData() | ||
|
|
||
| // Test with empty config - should default to ForbidRefAndRefs behavior | ||
| cfg := &references.Config{} | ||
|
|
||
| analyzer, err := references.Initializer().Init(cfg) | ||
| if err != nil { | ||
| t.Fatalf("initializing references linter: %v", err) | ||
| } | ||
|
|
||
| // With default config (empty Policy), it should default to ForbidRefAndRefs behavior | ||
| // So we test with folder 'b' which has the same expectations | ||
| analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "b") | ||
| } | ||
|
|
||
| func TestForbidRefAndRefs(t *testing.T) { | ||
| testdata := analysistest.TestData() | ||
|
|
||
| cfg := &references.Config{ | ||
| Policy: references.PolicyForbidRefAndRefs, | ||
| } | ||
|
|
||
| analyzer, err := references.Initializer().Init(cfg) | ||
| if err != nil { | ||
| t.Fatalf("initializing references linter: %v", err) | ||
| } | ||
|
|
||
| analysistest.RunWithSuggestedFixes(t, testdata, analyzer, "b") | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,34 @@ | ||||||
| /* | ||||||
| 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 references | ||||||
|
|
||||||
| // Policy defines the policy for handling Ref/Refs. | ||||||
| type Policy string | ||||||
|
|
||||||
| const ( | ||||||
| // PolicyAllowRefAndRefs allows Ref/Refs in field names. | ||||||
| PolicyAllowRefAndRefs Policy = "AllowRefAndRefs" | ||||||
| // PolicyForbidRefAndRefs forbids Ref/Refs in field names. | ||||||
| PolicyForbidRefAndRefs Policy = "ForbidRefAndRefs" | ||||||
| ) | ||||||
|
|
||||||
| // Config represents the configuration for the references linter. | ||||||
| type Config struct { | ||||||
| // Policy controls whether Ref/Refs are allowed or forbidden in field names. | ||||||
|
||||||
| // Policy controls whether Ref/Refs are allowed or forbidden in field names. | |
| // policy controls whether Ref/Refs are allowed or forbidden in field names. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| 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 `references` linter ensures that field names use 'Ref'/'Refs' instead of 'Reference'/'References'. | ||
|
|
||
| By default, `references` is enabled and enforces this naming convention. | ||
|
|
||
| The linter checks that 'Reference' anywhere in field names (beginning, middle, or end) is replaced with 'Ref'. | ||
| Similarly, 'References' anywhere in field names is replaced with 'Refs'. | ||
|
|
||
| Example configuration: | ||
|
|
||
| **Default behavior (forbid Ref/Refs in field names):** | ||
| ```yaml | ||
| lintersConfig: | ||
|
|
||
| references: | ||
| policy: ForbidRefAndRefs | ||
|
|
||
|
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. NIt, probably best to remove the blank lines in these code blocks 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. Not yet actioned |
||
| ``` | ||
|
|
||
| **For compatibility (allow Ref/Refs in field names):** | ||
| ```yaml | ||
| lintersConfig: | ||
|
|
||
| references: | ||
| policy: AllowRefAndRefs | ||
|
|
||
| ``` | ||
|
|
||
| When `policy` is set to `ForbidRefAndRefs` (the default), fields containing 'Ref' or 'Refs' anywhere in their names will be reported as errors. | ||
| This is useful to ensure consistency across the codebase. The policy can be set to `AllowRefAndRefs` to allow such field names. | ||
| */ | ||
| package references | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| /* | ||
| 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 references | ||
|
|
||
| import ( | ||
| "golang.org/x/tools/go/analysis" | ||
| "k8s.io/apimachinery/pkg/util/validation/field" | ||
| "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.NewConfigurableInitializer( | ||
| name, | ||
| initAnalyzer, | ||
| true, | ||
| validateConfig, | ||
| ) | ||
| } | ||
|
|
||
| func initAnalyzer(cfg *Config) (*analysis.Analyzer, error) { | ||
| return newAnalyzer(cfg), nil | ||
| } | ||
|
|
||
| // validateConfig validates the configuration for the references linter. | ||
| func validateConfig(cfg *Config, fldPath *field.Path) field.ErrorList { | ||
| if cfg == nil { | ||
| return nil // nil config is valid, will use defaults | ||
| } | ||
|
|
||
| var errs field.ErrorList | ||
|
|
||
| // Validate Policy enum if provided | ||
| if cfg.Policy != "" && cfg.Policy != PolicyAllowRefAndRefs && cfg.Policy != PolicyForbidRefAndRefs { | ||
|
||
| errs = append(errs, field.NotSupported( | ||
| fldPath.Child("policy"), | ||
| cfg.Policy, | ||
| []string{string(PolicyAllowRefAndRefs), string(PolicyForbidRefAndRefs)}, | ||
| )) | ||
| } | ||
|
|
||
| return errs | ||
| } | ||
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.
It looks like we've established a pattern that for linters associated with a "don't do this" semantic we prefix with
no.I think staying consistent would be good, so I wonder if this should instead be
noreference?@JoelSpeed what are your thoughts here?
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.
Yeah I think that makes sense,
noreferenceornoreferencesThere 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.
The only pushback I can think of on that, is that the others are forbidding the usage of certain types. Which means this one forbidding just the naming might be a bit different? Does it imply that you aren't allowed reference types vs fields with references in the name?
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.
We have
nophaseandnotimestampwhich are naming conventions