Skip to content

Commit acd0d00

Browse files
committed
Add preferredmarkers linter
Introduce a new configurable linter that enforces consistent marker usage across codebases by ensuring preferred marker identifiers are used instead of equivalent alternatives. Key Features: - Configurable marker preferences with multiple equivalent identifiers - Auto-fix support with expression preservation - Smart duplicate handling (consolidates multiple equivalents) - Type-aware checking (handles markers inherited from type aliases) - Deterministic output with sorted processing - Comprehensive configuration validation Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
1 parent a9a8d5c commit acd0d00

File tree

11 files changed

+1273
-0
lines changed

11 files changed

+1273
-0
lines changed

docs/linters.md

Lines changed: 100 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+
- [PreferredMarkers](#preferredmarkers) - Ensures preferred markers are used instead of equivalent markers
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
@@ -569,6 +570,105 @@ The `optionalorrequired` linter can automatically fix fields that are using the
569570

570571
It will also remove the secondary marker where both the preferred and secondary marker are present on a field.
571572

573+
## PreferredMarkers
574+
575+
The `preferredmarkers` linter ensures that types and fields use preferred markers instead of equivalent but different marker identifiers.
576+
577+
By default, `preferredmarkers` is not enabled.
578+
579+
This linter is useful for projects that want to enforce consistent marker usage across their codebase, especially when multiple equivalent markers exist. For example, Kubernetes has multiple ways to mark fields as optional:
580+
- `+optional`
581+
- `+k8s:optional`
582+
- `+kubebuilder:validation:Optional`
583+
584+
The linter can be configured to enforce using one preferred marker identifier and report any equivalent markers that should be replaced.
585+
586+
### Configuration
587+
588+
The linter requires a configuration that specifies preferred markers and their equivalent identifiers.
589+
590+
**Scenario:** Enforce using `+k8s:optional` instead of `+kubebuilder:validation:Optional`
591+
592+
```yaml
593+
linterConfig:
594+
preferredmarkers:
595+
markers:
596+
- preferredIdentifier: "k8s:optional"
597+
equivalentIdentifiers:
598+
- identifier: "kubebuilder:validation:Optional"
599+
```
600+
601+
**Scenario:** Enforce using a custom marker instead of multiple equivalent markers
602+
603+
```yaml
604+
linterConfig:
605+
preferredmarkers:
606+
markers:
607+
- preferredIdentifier: "custom:preferred"
608+
equivalentIdentifiers:
609+
- identifier: "custom:old:marker"
610+
- identifier: "custom:deprecated:marker"
611+
- identifier: "custom:legacy:marker"
612+
```
613+
614+
**Scenario:** Multiple preferred markers with different equivalents
615+
616+
```yaml
617+
linterConfig:
618+
preferredmarkers:
619+
markers:
620+
- preferredIdentifier: "k8s:optional"
621+
equivalentIdentifiers:
622+
- identifier: "kubebuilder:validation:Optional"
623+
- preferredIdentifier: "k8s:required"
624+
equivalentIdentifiers:
625+
- identifier: "kubebuilder:validation:Required"
626+
```
627+
628+
The linter checks both type-level and field-level markers, including markers inherited from type aliases.
629+
630+
### Fixes
631+
632+
When one or more equivalent markers are found, the linter will:
633+
634+
1. Report a diagnostic message indicating which marker(s) should be preferred
635+
2. Suggest a fix that:
636+
- If the preferred marker does not already exist: replaces the first equivalent marker with the preferred identifier and preserves any marker expressions (e.g., `=value` or `:key=value`)
637+
- If the preferred marker already exists: removes all equivalent markers to avoid duplicates
638+
- Removes any additional equivalent markers
639+
640+
**Example 1:** If both `+kubebuilder:validation:Optional` and `+custom:optional` are configured as equivalents to `+k8s:optional`, the following code:
641+
642+
```go
643+
// +kubebuilder:validation:Optional
644+
// +custom:optional
645+
type MyType string
646+
```
647+
648+
will be automatically fixed to:
649+
650+
```go
651+
// +k8s:optional
652+
type MyType string
653+
```
654+
655+
**Example 2:** If the preferred marker already exists alongside equivalent markers:
656+
657+
```go
658+
// +k8s:optional
659+
// +kubebuilder:validation:Optional
660+
type MyType string
661+
```
662+
663+
will be automatically fixed to:
664+
665+
```go
666+
// +k8s:optional
667+
type MyType string
668+
```
669+
670+
Marker expressions are preserved during replacement. For example, `+kubebuilder:validation:Optional:=someValue` becomes `+k8s:optional=someValue`. Note that unnamed expressions (`:=value`) are normalized to use `=value` syntax for universal compatibility across different marker systems.
671+
572672
## RequiredFields
573673

574674
The `requiredfields` linter checks that all fields marked as required adhere to having `omitempty` or `omitzero` values in their `json` tags.

0 commit comments

Comments
 (0)