Skip to content

Commit 9618c3c

Browse files
committed
refactor(markerscope): convert config to PascalCase values and named object lists
Signed-off-by: nayuta-ai <nayuta723@gmail.com>
1 parent 35a6b7b commit 9618c3c

File tree

7 files changed

+128
-88
lines changed

7 files changed

+128
-88
lines changed

docs/linters.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -442,25 +442,25 @@ lintersConfig:
442442
allowDangerousTypes: false # Allow dangerous number types (float32, float64). Defaults to `false`.
443443
markerRules:
444444
# Override default rule for a built-in marker
445-
"optional":
446-
scope: field # or: type, any
445+
- name: "optional"
446+
scope: Field # or: Type, Any
447447

448448
# Add a custom marker with scope constraint only
449-
"mycompany:validation:CustomMarker":
450-
scope: any
449+
- name: "mycompany:validation:CustomMarker"
450+
scope: Any
451451

452452
# Add a custom marker with scope and type constraints
453-
"mycompany:validation:NumericLimit":
454-
scope: any
453+
- name: "mycompany:validation:NumericLimit"
454+
scope: Any
455455
strictTypeConstraint: true # Require declaration on type definition for named types
456456
typeConstraint:
457457
allowedSchemaTypes:
458458
- integer
459459
- number
460460

461461
# Add a custom array items marker with element type constraint
462-
"mycompany:validation:items:StringFormat":
463-
scope: any
462+
- name: "mycompany:validation:items:StringFormat"
463+
scope: Any
464464
typeConstraint:
465465
allowedSchemaTypes:
466466
- array
@@ -470,9 +470,9 @@ lintersConfig:
470470
```
471471
472472
**Scope values:**
473-
- `field`: FieldScope - marker can only be on fields
474-
- `type`: TypeScope - marker can only be on types
475-
- `any`: AnyScope - marker can be on fields or types
473+
- `Field`: FieldScope - marker can only be on fields
474+
- `Type`: TypeScope - marker can only be on types
475+
- `Any`: AnyScope - marker can be on fields or types
476476

477477
**Type constraint fields:**
478478
- `allowedSchemaTypes`: List of allowed OpenAPI schema types (`integer`, `number`, `string`, `boolean`, `array`, `object`)

pkg/analysis/markerscope/analyzer.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ func newAnalyzer(cfg *MarkerScopeConfig) *analysis.Analyzer {
6060
cfg = &MarkerScopeConfig{}
6161
}
6262

63+
// Convert list of marker rules to map
64+
customRules := markerRulesListToMap(cfg.MarkerRules)
65+
6366
a := &analyzer{
64-
markerRules: mergeMarkerRules(DefaultMarkerRules(), cfg.MarkerRules),
67+
markerRules: mergeMarkerRules(DefaultMarkerRules(), customRules),
6568
policy: cfg.Policy,
6669
allowDangerousTypes: cfg.AllowDangerousTypes,
6770
}
@@ -87,6 +90,17 @@ func newAnalyzer(cfg *MarkerScopeConfig) *analysis.Analyzer {
8790
}
8891
}
8992

93+
// markerRulesListToMap converts a list of marker rules to a map keyed by marker name.
94+
func markerRulesListToMap(rules []MarkerScopeRule) map[string]MarkerScopeRule {
95+
result := make(map[string]MarkerScopeRule, len(rules))
96+
for _, rule := range rules {
97+
if rule.Name != "" {
98+
result[rule.Name] = rule
99+
}
100+
}
101+
return result
102+
}
103+
90104
// mergeMarkerRules merges custom marker rules with default marker rules.
91105
// Custom rules take precedence over default rules for the same marker.
92106
func mergeMarkerRules(defaults, custom map[string]MarkerScopeRule) map[string]MarkerScopeRule {

pkg/analysis/markerscope/analyzer_test.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,36 @@ func TestAnalyzerWithCustomMarkers(t *testing.T) {
4343
testdata := analysistest.TestData()
4444
cfg := &MarkerScopeConfig{
4545
Policy: MarkerScopePolicyWarn,
46-
MarkerRules: map[string]MarkerScopeRule{
46+
MarkerRules: []MarkerScopeRule{
4747
// Custom field-only marker
48-
"custom:field-only": {
48+
{
49+
Name: "custom:field-only",
4950
Scope: FieldScope,
5051
},
5152
// Custom type-only marker
52-
"custom:type-only": {
53+
{
54+
Name: "custom:type-only",
5355
Scope: TypeScope,
5456
},
5557
// Custom marker with string type constraint
56-
"custom:string-only": {
58+
{
59+
Name: "custom:string-only",
5760
Scope: FieldScope,
5861
TypeConstraint: &TypeConstraint{
5962
AllowedSchemaTypes: []SchemaType{SchemaTypeString},
6063
},
6164
},
6265
// Custom marker with integer type constraint
63-
"custom:integer-only": {
66+
{
67+
Name: "custom:integer-only",
6468
Scope: FieldScope,
6569
TypeConstraint: &TypeConstraint{
6670
AllowedSchemaTypes: []SchemaType{SchemaTypeInteger},
6771
},
6872
},
6973
// Custom marker with array of strings constraint
70-
"custom:string-array": {
74+
{
75+
Name: "custom:string-array",
7176
Scope: FieldScope,
7277
TypeConstraint: &TypeConstraint{
7378
AllowedSchemaTypes: []SchemaType{SchemaTypeArray},

pkg/analysis/markerscope/config.go

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,24 @@ import (
2121
"sigs.k8s.io/kube-api-linter/pkg/markers"
2222
)
2323

24-
// ScopeConstraint defines where a marker is allowed to be placed using bit flags.
25-
type ScopeConstraint uint8
24+
// ScopeConstraint defines where a marker is allowed to be placed.
25+
type ScopeConstraint string
2626

2727
const (
2828
// FieldScope indicates the marker can be placed on fields.
29-
FieldScope ScopeConstraint = 1 << iota
29+
FieldScope ScopeConstraint = "Field"
3030
// TypeScope indicates the marker can be placed on type definitions.
31-
TypeScope
32-
31+
TypeScope ScopeConstraint = "Type"
3332
// AnyScope indicates the marker can be placed on either fields or types.
34-
AnyScope = FieldScope | TypeScope
33+
AnyScope ScopeConstraint = "Any"
3534
)
3635

37-
// String returns a human-readable representation of the scope constraint.
38-
func (s ScopeConstraint) String() string {
39-
switch s {
40-
case FieldScope:
41-
return "field"
42-
case TypeScope:
43-
return "type"
44-
case AnyScope:
45-
return "any"
46-
default:
47-
return "unknown"
48-
}
49-
}
50-
5136
// Allows checks if the given scope is allowed by this constraint.
5237
func (s ScopeConstraint) Allows(scope ScopeConstraint) bool {
53-
return s&scope != 0
38+
if s == AnyScope {
39+
return true
40+
}
41+
return s == scope
5442
}
5543

5644
// TypeConstraint defines what types a marker can be applied to.
@@ -69,6 +57,10 @@ type TypeConstraint struct {
6957

7058
// MarkerScopeRule defines comprehensive scope validation rules for a marker.
7159
type MarkerScopeRule struct {
60+
// Name is the marker identifier (e.g., "optional", "kubebuilder:validation:Minimum").
61+
// This field is only used when MarkerScopeRule is part of a list configuration.
62+
Name string `json:"name,omitempty"`
63+
7264
// Scope specifies where the marker can be placed (field vs type).
7365
Scope ScopeConstraint
7466

@@ -89,29 +81,29 @@ type MarkerScopePolicy string
8981

9082
const (
9183
// MarkerScopePolicyWarn only reports warnings without suggesting fixes.
92-
MarkerScopePolicyWarn MarkerScopePolicy = "warn"
84+
MarkerScopePolicyWarn MarkerScopePolicy = "Warn"
9385

9486
// MarkerScopePolicySuggestFix reports warnings and suggests automatic fixes.
95-
MarkerScopePolicySuggestFix MarkerScopePolicy = "suggest_fix"
87+
MarkerScopePolicySuggestFix MarkerScopePolicy = "SuggestFix"
9688
)
9789

9890
// MarkerScopeConfig contains configuration for marker scope validation.
9991
type MarkerScopeConfig struct {
100-
// MarkerRules maps marker names to their scope rules with scope and type constraints.
101-
// This map can be used to:
92+
// MarkerRules is a list of marker rules with scope and type constraints.
93+
// This list can be used to:
10294
// - Override default rules for built-in markers (from DefaultMarkerRules)
10395
// - Add rules for custom markers not included in DefaultMarkerRules
10496
//
105-
// If a marker is not in this map AND not in DefaultMarkerRules(), no scope validation is performed.
106-
// If a marker is in both this map and DefaultMarkerRules(), this map takes precedence.
97+
// If a marker is not in this list AND not in DefaultMarkerRules(), no scope validation is performed.
98+
// If a marker is in both this list and DefaultMarkerRules(), this list takes precedence.
10799
//
108100
// Example: Adding a custom marker
109101
// markerRules:
110-
// "mycompany:validation:CustomMarker":
102+
// - name: "mycompany:validation:CustomMarker"
111103
// scope: any
112104
// typeConstraint:
113105
// allowedSchemaTypes: ["string"]
114-
MarkerRules map[string]MarkerScopeRule `json:"markerRules,omitempty"`
106+
MarkerRules []MarkerScopeRule `json:"markerRules,omitempty"`
115107

116108
// AllowDangerousTypes specifies if dangerous types are allowed.
117109
// If true, dangerous types are allowed.

pkg/analysis/markerscope/errors.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,18 @@ import (
2121
)
2222

2323
var (
24-
errScopeNonZero = errors.New("scope must be non-zero")
25-
errInvalidScopeBits = errors.New("invalid scope bits")
24+
errScopeRequired = errors.New("scope is required")
2625
)
2726

27+
// InvalidScopeConstraintError represents an error when a scope constraint is invalid.
28+
type InvalidScopeConstraintError struct {
29+
Scope string
30+
}
31+
32+
func (e *InvalidScopeConstraintError) Error() string {
33+
return fmt.Sprintf("invalid scope: %q (must be one of: Field, Type, Any)", e.Scope)
34+
}
35+
2836
// InvalidSchemaTypeError represents an error when a schema type is invalid.
2937
type InvalidSchemaTypeError struct {
3038
SchemaType string

pkg/analysis/markerscope/initializer.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,17 @@ func validateConfig(cfg *MarkerScopeConfig, fldPath *field.Path) field.ErrorList
5858
}
5959

6060
// Validate marker rules
61-
for marker, rule := range cfg.MarkerRules {
61+
for i, rule := range cfg.MarkerRules {
62+
markerRulePath := fldPath.Child("markerRules").Index(i)
63+
64+
// Validate that name is not empty
65+
if rule.Name == "" {
66+
fieldErrors = append(fieldErrors, field.Required(markerRulePath.Child("name"), "marker name is required"))
67+
continue
68+
}
69+
6270
if err := validateMarkerRule(rule); err != nil {
63-
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("markerRules", marker), rule, err.Error()))
71+
fieldErrors = append(fieldErrors, field.Invalid(markerRulePath, rule, err.Error()))
6472
}
6573
}
6674

@@ -69,14 +77,16 @@ func validateConfig(cfg *MarkerScopeConfig, fldPath *field.Path) field.ErrorList
6977

7078
func validateMarkerRule(rule MarkerScopeRule) error {
7179
// Validate scope constraint
72-
if rule.Scope == 0 {
73-
return errScopeNonZero
80+
if rule.Scope == "" {
81+
return errScopeRequired
7482
}
7583

76-
// Validate that scope is a valid combination of FieldScope and/or TypeScope
77-
validScopes := FieldScope | TypeScope
78-
if rule.Scope&^validScopes != 0 {
79-
return errInvalidScopeBits
84+
// Validate that scope is a valid value
85+
switch rule.Scope {
86+
case FieldScope, TypeScope, AnyScope:
87+
// Valid scope
88+
default:
89+
return &InvalidScopeConstraintError{Scope: string(rule.Scope)}
8090
}
8191

8292
// Validate type constraint if present

0 commit comments

Comments
 (0)