Skip to content

Commit 9da4335

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

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
@@ -292,39 +292,113 @@ lintersConfig:
292292

293293
## MarkerScope
294294

295-
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.
295+
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.
296+
297+
The linter performs two levels of validation:
298+
299+
1. **Scope validation**: Ensures markers are placed on the correct location (field vs type)
300+
2. **Type constraint validation**: Ensures markers are applied to compatible data types (e.g., numeric markers on numeric types only)
301+
302+
### Scope Types
296303

297304
The linter defines different scope types for markers:
298305

299-
- **Field-only markers**: Can only be applied to struct fields (e.g., `required`, `kubebuilder:validation:Required`)
300-
- **Type-only markers**: Can only be applied to type definitions
301-
- **Type or Map/Slice fields**: Can be applied to type definitions, map fields, or slice fields (e.g., `kubebuilder:validation:MinProperties`)
302-
- **Field or Type markers**: Can be applied to either fields or type definitions
306+
- **FieldScope**: Can only be applied to struct fields (e.g., `optional`, `required`, `nullable`)
307+
- **TypeScope**: Can only be applied to type definitions (e.g., `kubebuilder:validation:items:ExactlyOneOf`)
308+
- **AnyScope**: Can be applied to either fields or type definitions (e.g., `kubebuilder:validation:Minimum`, `kubebuilder:validation:Pattern`)
309+
310+
### Type Constraints
311+
312+
The linter validates that markers are applied to compatible OpenAPI schema types:
313+
314+
- **Numeric markers** (`Minimum`, `Maximum`, `MultipleOf`): Only for `integer` or `number` types
315+
- **String markers** (`Pattern`, `MinLength`, `MaxLength`): Only for `string` types
316+
- **Array markers** (`MinItems`, `MaxItems`, `UniqueItems`): Only for `array` types
317+
- **Object markers** (`MinProperties`, `MaxProperties`): Only for `object` types (struct/map)
318+
- **Array items markers** (`items:Minimum`, `items:Pattern`, etc.): Apply constraints to array element types
319+
320+
OpenAPI schema types map to Go types as follows:
321+
- `integer`: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64
322+
- `number`: float32, float64
323+
- `string`: string
324+
- `boolean`: bool
325+
- `array`: []T, [N]T (slices and arrays)
326+
- `object`: struct, map[K]V
327+
328+
### Default Marker Rules
303329

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

306-
By default, the linter enforces these scope rules:
332+
**Field-only markers:**
333+
- `optional`, `required`, `nullable`
334+
- `kubebuilder:default`, `kubebuilder:validation:Example`
307335

308-
- `required` and `kubebuilder:validation:Required`: Field-only
309-
- `kubebuilder:validation:MinProperties`: Type definitions, map fields, or slice fields only
336+
**Type-only markers:**
337+
- `kubebuilder:validation:items:ExactlyOneOf`
338+
- `kubebuilder:validation:items:AtMostOneOf`
339+
- `kubebuilder:validation:items:AtLeastOneOf`
340+
341+
**AnyScope markers with type constraints:**
342+
- `kubebuilder:validation:Minimum` (integer/number types only)
343+
- `kubebuilder:validation:Pattern` (string types only)
344+
- `kubebuilder:validation:MinItems` (array types only)
345+
- `kubebuilder:validation:MinProperties` (object types only)
346+
347+
**AnyScope markers without type constraints:**
348+
- `kubebuilder:validation:Enum`, `kubebuilder:validation:Format`
349+
- `kubebuilder:pruning:PreserveUnknownFields`, `kubebuilder:title`
310350

311351
### Configuration
312352

353+
You can customize marker rules or add support for custom markers:
354+
313355
```yaml
314356
lintersConfig:
315357
markerscope:
316-
policy: SuggestFix | Warn # The policy for marker scope violations. Defaults to `SuggestFix`.
358+
policy: Warn | SuggestFix # The policy for marker scope violations. Defaults to `Warn`.
359+
markerRules:
360+
# Override default rule for a built-in marker
361+
"optional":
362+
scope: field # or: type, any
363+
364+
# Add a custom marker with scope constraint only
365+
"mycompany:validation:CustomMarker":
366+
scope: any
367+
368+
# Add a custom marker with scope and type constraints
369+
"mycompany:validation:NumericLimit":
370+
scope: any
371+
typeConstraint:
372+
allowedSchemaTypes:
373+
- integer
374+
- number
375+
376+
# Add a custom array items marker with element type constraint
377+
"mycompany:validation:items:StringFormat":
378+
scope: any
379+
typeConstraint:
380+
allowedSchemaTypes:
381+
- array
382+
elementConstraint:
383+
allowedSchemaTypes:
384+
- string
317385
```
318386
319-
### Fixes
387+
**Scope values:**
388+
- `field`: FieldScope - marker can only be on fields
389+
- `type`: TypeScope - marker can only be on types
390+
- `any`: AnyScope - marker can be on fields or types
320391

321-
The `markerscope` linter can automatically fix scope violations when `policy` is set to `SuggestFix`:
392+
**Type constraint fields:**
393+
- `allowedSchemaTypes`: List of allowed OpenAPI schema types (`integer`, `number`, `string`, `boolean`, `array`, `object`)
394+
- `elementConstraint`: Nested constraint for array element types (only valid when `allowedSchemaTypes` includes `array`)
395+
396+
If a marker is not in `markerRules` and not in the default rules, no validation is performed for that marker.
397+
If a marker is in both `markerRules` and the default rules, your configuration takes precedence.
398+
399+
### Fixes
322400

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

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

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)