Skip to content

Commit c096c8f

Browse files
committed
add custom marker rule setting
Signed-off-by: nayuta-ai <nayuta723@gmail.com>
1 parent cb5218f commit c096c8f

File tree

3 files changed

+129
-26
lines changed

3 files changed

+129
-26
lines changed

docs/linters.md

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -346,39 +346,113 @@ lintersConfig:
346346

347347
## MarkerScope
348348

349-
The `markerscope` linter validates that markers are applied in the correct scope. It ensures that markers are placed on appropriate Go language constructs (types, fields) according to their intended usage.
349+
The `markerscope` linter validates that markers are applied in the correct scope and to the correct types. It ensures that markers are placed on appropriate Go language constructs (types, fields) and applied to compatible data types according to their intended usage.
350+
351+
The linter performs two levels of validation:
352+
353+
1. **Scope validation**: Ensures markers are placed on the correct location (field vs type)
354+
2. **Type constraint validation**: Ensures markers are applied to compatible data types (e.g., numeric markers on numeric types only)
355+
356+
### Scope Types
350357

351358
The linter defines different scope types for markers:
352359

353-
- **Field-only markers**: Can only be applied to struct fields (e.g., `required`, `kubebuilder:validation:Required`)
354-
- **Type-only markers**: Can only be applied to type definitions
355-
- **Type or Map/Slice fields**: Can be applied to type definitions, map fields, or slice fields (e.g., `kubebuilder:validation:MinProperties`)
356-
- **Field or Type markers**: Can be applied to either fields or type definitions
360+
- **FieldScope**: Can only be applied to struct fields (e.g., `optional`, `required`, `nullable`)
361+
- **TypeScope**: Can only be applied to type definitions (e.g., `kubebuilder:validation:items:ExactlyOneOf`)
362+
- **AnyScope**: Can be applied to either fields or type definitions (e.g., `kubebuilder:validation:Minimum`, `kubebuilder:validation:Pattern`)
363+
364+
### Type Constraints
365+
366+
The linter validates that markers are applied to compatible OpenAPI schema types:
367+
368+
- **Numeric markers** (`Minimum`, `Maximum`, `MultipleOf`): Only for `integer` or `number` types
369+
- **String markers** (`Pattern`, `MinLength`, `MaxLength`): Only for `string` types
370+
- **Array markers** (`MinItems`, `MaxItems`, `UniqueItems`): Only for `array` types
371+
- **Object markers** (`MinProperties`, `MaxProperties`): Only for `object` types (struct/map)
372+
- **Array items markers** (`items:Minimum`, `items:Pattern`, etc.): Apply constraints to array element types
373+
374+
OpenAPI schema types map to Go types as follows:
375+
- `integer`: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
376+
- `number`: float32, float64
377+
- `string`: string
378+
- `boolean`: bool
379+
- `array`: []T, [N]T (slices and arrays)
380+
- `object`: struct, map[K]V
381+
382+
### Default Marker Rules
357383

358-
### Default Scope Rules
384+
The linter includes built-in rules for all standard kubebuilder markers and k8s declarative validation markers. Examples:
359385

360-
By default, the linter enforces these scope rules:
386+
**Field-only markers:**
387+
- `optional`, `required`, `nullable`
388+
- `kubebuilder:default`, `kubebuilder:validation:Example`
361389

362-
- `required` and `kubebuilder:validation:Required`: Field-only
363-
- `kubebuilder:validation:MinProperties`: Type definitions, map fields, or slice fields only
390+
**Type-only markers:**
391+
- `kubebuilder:validation:items:ExactlyOneOf`
392+
- `kubebuilder:validation:items:AtMostOneOf`
393+
- `kubebuilder:validation:items:AtLeastOneOf`
394+
395+
**AnyScope markers with type constraints:**
396+
- `kubebuilder:validation:Minimum` (integer/number types only)
397+
- `kubebuilder:validation:Pattern` (string types only)
398+
- `kubebuilder:validation:MinItems` (array types only)
399+
- `kubebuilder:validation:MinProperties` (object types only)
400+
401+
**AnyScope markers without type constraints:**
402+
- `kubebuilder:validation:Enum`, `kubebuilder:validation:Format`
403+
- `kubebuilder:pruning:PreserveUnknownFields`, `kubebuilder:title`
364404

365405
### Configuration
366406

407+
You can customize marker rules or add support for custom markers:
408+
367409
```yaml
368410
lintersConfig:
369411
markerscope:
370-
policy: SuggestFix | Warn # The policy for marker scope violations. Defaults to `SuggestFix`.
412+
policy: Warn | SuggestFix # The policy for marker scope violations. Defaults to `Warn`.
413+
markerRules:
414+
# Override default rule for a built-in marker
415+
"optional":
416+
scope: field # or: type, any
417+
418+
# Add a custom marker with scope constraint only
419+
"mycompany:validation:CustomMarker":
420+
scope: any
421+
422+
# Add a custom marker with scope and type constraints
423+
"mycompany:validation:NumericLimit":
424+
scope: any
425+
typeConstraint:
426+
allowedSchemaTypes:
427+
- integer
428+
- number
429+
430+
# Add a custom array items marker with element type constraint
431+
"mycompany:validation:items:StringFormat":
432+
scope: any
433+
typeConstraint:
434+
allowedSchemaTypes:
435+
- array
436+
elementConstraint:
437+
allowedSchemaTypes:
438+
- string
371439
```
372440
373-
### Fixes
441+
**Scope values:**
442+
- `field`: FieldScope - marker can only be on fields
443+
- `type`: TypeScope - marker can only be on types
444+
- `any`: AnyScope - marker can be on fields or types
374445

375-
The `markerscope` linter can automatically fix scope violations when `policy` is set to `SuggestFix`:
446+
**Type constraint fields:**
447+
- `allowedSchemaTypes`: List of allowed OpenAPI schema types (`integer`, `number`, `string`, `boolean`, `array`, `object`)
448+
- `elementConstraint`: Nested constraint for array element types (only valid when `allowedSchemaTypes` includes `array`)
449+
450+
If a marker is not in `markerRules` and not in the default rules, no validation is performed for that marker.
451+
If a marker is in both `markerRules` and the default rules, your configuration takes precedence.
452+
453+
### Fixes
376454

377-
1. **Remove incorrect markers**: Suggests removing markers that are in the wrong scope
378-
2. **Move markers to correct locations**:
379-
- Move field-only markers from types to appropriate fields
380-
- Move type-only markers from fields to their corresponding type definitions
381-
3. **Preserve marker values**: When moving markers like `kubebuilder:validation:MinProperties=1`
455+
The `markerscope` linter does not currently provide automatic fixes. It reports violations as warnings or errors based on the configured policy.
382456

383457
**Note**: This linter is not enabled by default and must be explicitly enabled in the configuration.
384458

pkg/analysis/markerscope/analyzer.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333
name = "markerscope"
3434
)
3535

36+
// TODO: SuggestFix
3637
func init() {
3738
// Register all markers we want to validate scope for
3839
defaults := DefaultMarkerRules()
@@ -55,17 +56,10 @@ func newAnalyzer(cfg *MarkerScopeConfig) *analysis.Analyzer {
5556
}
5657

5758
a := &analyzer{
58-
markerRules: DefaultMarkerRules(),
59+
markerRules: mergeMarkerRules(DefaultMarkerRules(), cfg.MarkerRules),
5960
policy: cfg.Policy,
6061
}
6162

62-
// Override with custom rules if provided
63-
if cfg.MarkerRules != nil {
64-
for marker, rule := range cfg.MarkerRules {
65-
a.markerRules[marker] = rule
66-
}
67-
}
68-
6963
// Set default policy if not specified
7064
if a.policy == "" {
7165
a.policy = MarkerScopePolicyWarn
@@ -80,6 +74,24 @@ func newAnalyzer(cfg *MarkerScopeConfig) *analysis.Analyzer {
8074
}
8175
}
8276

77+
// mergeMarkerRules merges custom marker rules with default marker rules.
78+
// Custom rules take precedence over default rules for the same marker.
79+
func mergeMarkerRules(defaults, custom map[string]MarkerScopeRule) map[string]MarkerScopeRule {
80+
merged := make(map[string]MarkerScopeRule, len(defaults)+len(custom))
81+
82+
// Copy all default rules
83+
for marker, rule := range defaults {
84+
merged[marker] = rule
85+
}
86+
87+
// Override with custom rules
88+
for marker, rule := range custom {
89+
merged[marker] = rule
90+
}
91+
92+
return merged
93+
}
94+
8395
func (a *analyzer) run(pass *analysis.Pass) (any, error) {
8496
inspect, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
8597
if !ok {

pkg/analysis/markerscope/config.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,31 @@ const (
109109
// MarkerScopeConfig contains configuration for marker scope validation.
110110
type MarkerScopeConfig struct {
111111
// MarkerRules maps marker names to their scope rules with scope and type constraints.
112-
// If a marker is not in this map, no scope validation is performed.
112+
// This map can be used to:
113+
// - Override default rules for built-in markers (from DefaultMarkerRules)
114+
// - Add rules for custom markers not included in DefaultMarkerRules
115+
//
116+
// If a marker is not in this map AND not in DefaultMarkerRules(), no scope validation is performed.
117+
// If a marker is in both this map and DefaultMarkerRules(), this map takes precedence.
118+
//
119+
// Example: Adding a custom marker
120+
// markerRules:
121+
// "mycompany:validation:CustomMarker":
122+
// scope: any
123+
// typeConstraint:
124+
// allowedSchemaTypes: ["string"]
113125
MarkerRules map[string]MarkerScopeRule `json:"markerRules,omitempty"`
114126

115127
// Policy determines whether to suggest fixes or just warn.
116128
Policy MarkerScopePolicy `json:"policy,omitempty"`
117129
}
118130

119131
// DefaultMarkerRules returns the default marker scope rules with type constraints.
132+
// These rules are based on kubebuilder markers and k8s declarative validation markers.
133+
//
134+
// Users can override these rules or add custom markers by providing a MarkerScopeConfig
135+
// with MarkerRules that will be merged with (and take precedence over) these defaults.
136+
//
120137
// ref: https://github.com/kubernetes-sigs/controller-tools/blob/v0.19.0/pkg/crd/markers/
121138
func DefaultMarkerRules() map[string]MarkerScopeRule {
122139
return map[string]MarkerScopeRule{

0 commit comments

Comments
 (0)