Skip to content

Commit 4652204

Browse files
committed
fixup! finalize implementation
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 1d4846e commit 4652204

File tree

11 files changed

+604
-103
lines changed

11 files changed

+604
-103
lines changed

docs/linters.md

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
- [Conditions](#conditions) - Checks that `Conditions` fields are correctly formatted
44
- [CommentStart](#commentstart) - Ensures comments start with the serialized form of the type
55
- [DuplicateMarkers](#duplicatemarkers) - Checks for exact duplicates of markers
6+
- [ForbiddenMarkers](#forbiddenmarkers) - Checks that no forbidden markers are present on types/fields.
67
- [Integers](#integers) - Validates usage of supported integer types
78
- [JSONTags](#jsontags) - Ensures proper JSON tag formatting
89
- [MaxLength](#maxlength) - Checks for maximum length constraints on strings and arrays
910
- [NoBools](#nobools) - Prevents usage of boolean types
1011
- [NoFloats](#nofloats) - Prevents usage of floating-point types
1112
- [Nomaps](#nomaps) - Restricts usage of map types
13+
- [NoNullable](#nonullable) - Prevents usage of the nullable marker
1214
- [Nophase](#nophase) - Prevents usage of 'Phase' fields
1315
- [Notimestamp](#notimestamp) - Prevents usage of 'TimeStamp' fields
1416
- [OptionalFields](#optionalfields) - Validates optional field conventions
@@ -98,6 +100,62 @@ will not.
98100
The `duplicatemarkers` linter can automatically fix all markers that are exact match to another markers.
99101
If there are duplicates across fields and their underlying type, the marker on the type will be preferred and the marker on the field will be removed.
100102

103+
## ForbiddenMarkers
104+
105+
The `forbiddenmarkers` linter ensures that types and fields do not contain any markers that are forbidden.
106+
107+
By default, `forbiddenmarkers` is not enabled.
108+
109+
### Configuation
110+
111+
It can be configured with a list of marker identifiers and optionally their attributes and values that are forbidden
112+
113+
```yaml
114+
lintersConfig:
115+
forbiddenMarkers:
116+
markers:
117+
- identifier: forbidden:marker
118+
- identifier: forbidden:withAttribute
119+
attributes:
120+
- name: fruit
121+
- identifier: forbidden:withMultipleAttributes
122+
attributes:
123+
- name: fruit
124+
- name: color
125+
- identifier: forbidden:withAttributeValues
126+
attributes:
127+
- name: fruit
128+
values:
129+
- apple
130+
- banana
131+
- orange
132+
- identifier: forbidden:withMultipleAttributesValues
133+
attributes:
134+
- name: fruit
135+
values:
136+
- apple
137+
- banana
138+
- orange
139+
- name: color
140+
values:
141+
- red
142+
- green
143+
- blue
144+
```
145+
146+
Using the config above, the following examples would be forbidden:
147+
148+
- `+forbidden:marker` (all instances, including if they have attributes and values)
149+
- `+forbidden:withAttribute:fruit=*,*` (all instances of this marker containing the attribute 'fruit')
150+
- `+forbidden:withMultipleAttributes:fruit=*,color=*,*` (all instances of this marker containing both the 'fruit' AND 'color' attributes)
151+
- `+forbidden:withAttributeValues:fruit={apple || banana || orange},*` (all instances of this marker containing the 'fruit' attribute where the value is set to one of 'apple', 'banana', or 'orange')
152+
153+
- `+forbidden:withMultipleAttributesValues:fruit={apple || banana || orange},color={red || green || blue},*` (all instances of this marker containing the 'fruit' attribute where the value is set to one of 'apple', 'banana', or 'orange' AND the 'color' attribute where the value is set to one of 'red', 'green', or 'blue')
154+
155+
### Fixes
156+
157+
Fixes are suggested to remove all markers that are forbidden.
158+
101159
## Integers
102160

103161
The `integers` linter checks for usage of unsupported integer types.
@@ -161,6 +219,14 @@ lintersConfig:
161219
policy: Enforce | AllowStringToStringMaps | Ignore # Determines how the linter should handle maps of simple types. Defaults to AllowStringToStringMaps.
162220
```
163221

222+
## NoNullable
223+
224+
The `nonullable` linter ensures that types and fields do not have the `nullable` marker.
225+
226+
### Fixes
227+
228+
Fixes are suggested to remove the `nullable` marker.
229+
164230
## Notimestamp
165231

166232
The `notimestamp` linter checks that the fields in the API are not named with the word 'Timestamp'.
@@ -334,10 +400,12 @@ linter will suggest adding the comment `// +kubebuilder:subresource:status` abov
334400

335401
The `uniquemarkers` linter ensures that types and fields do not contain more than a single definition of a marker that should only be present once.
336402

337-
Because this linter has no way of determining which marker definition was intended it does not suggest any fixes
403+
Because this linter has no way of determining which marker definition was intended it does not suggest any fixes
338404

339405
### Configuration
406+
340407
It can configured to include a set of custom markers in the analysis by setting:
408+
341409
```yaml
342410
lintersConfig:
343411
uniquemarkers:

pkg/analysis/forbiddenmarkers/analyzer.go

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,17 @@ import (
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2626
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2727
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
28-
"sigs.k8s.io/kube-api-linter/pkg/config"
2928
)
3029

3130
const name = "forbiddenmarkers"
3231

3332
type analyzer struct {
34-
forbiddenMarkers []config.ForbiddenMarker
35-
}
36-
37-
// ForbiddenMarkersOptions is a function that configures the
38-
// forbiddenmarkers analysis.Analyzer
39-
type ForbiddenMarkersOption func(a *analysis.Analyzer)
40-
41-
// WithName sets the name of the forbiddenmarkers analysis.Analyzer
42-
func WithName(name string) ForbiddenMarkersOption {
43-
return func(a *analysis.Analyzer) {
44-
a.Name = name
45-
}
46-
}
47-
48-
// WithDoc sets the doc string of the forbiddenmarkers analysis.Analyzer
49-
func WithDoc(doc string) ForbiddenMarkersOption {
50-
return func(a *analysis.Analyzer) {
51-
a.Doc = doc
52-
}
33+
forbiddenMarkers []Marker
5334
}
5435

5536
// NewAnalyzer creates a new analysis.Analyzer for the forbiddenmarkers
5637
// linter based on the provided config.ForbiddenMarkersConfig.
57-
func NewAnalyzer(cfg config.ForbiddenMarkersConfig, opts ...ForbiddenMarkersOption) *analysis.Analyzer {
38+
func newAnalyzer(cfg *Config) *analysis.Analyzer {
5839
a := &analyzer{
5940
forbiddenMarkers: cfg.Markers,
6041
}
@@ -66,8 +47,8 @@ func NewAnalyzer(cfg config.ForbiddenMarkersConfig, opts ...ForbiddenMarkersOpti
6647
Requires: []*analysis.Analyzer{inspector.Analyzer},
6748
}
6849

69-
for _, opt := range opts {
70-
opt(analyzer)
50+
for _, marker := range a.forbiddenMarkers {
51+
markers.DefaultRegistry().Register(marker.Identifier)
7152
}
7253

7354
return analyzer
@@ -90,7 +71,7 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
9071
return nil, nil //nolint:nilnil
9172
}
9273

93-
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, forbiddenMarkers []config.ForbiddenMarker) {
74+
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, forbiddenMarkers []Marker) {
9475
if field == nil || len(field.Names) == 0 {
9576
return
9677
}
@@ -99,7 +80,7 @@ func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Mar
9980
check(markers, forbiddenMarkers, reportField(pass, field))
10081
}
10182

102-
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, forbiddenMarkers []config.ForbiddenMarker) {
83+
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, forbiddenMarkers []Marker) {
10384
if typeSpec == nil {
10485
return
10586
}
@@ -108,7 +89,7 @@ func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess marker
10889
check(markers, forbiddenMarkers, reportType(pass, typeSpec))
10990
}
11091

111-
func check(markerSet markers.MarkerSet, forbiddenMarkers []config.ForbiddenMarker, reportFunc func(marker markers.Marker)) {
92+
func check(markerSet markers.MarkerSet, forbiddenMarkers []Marker, reportFunc func(marker markers.Marker)) {
11293
for _, marker := range forbiddenMarkers {
11394
marks := markerSet.Get(marker.Identifier)
11495
for _, mark := range marks {
@@ -119,12 +100,10 @@ func check(markerSet markers.MarkerSet, forbiddenMarkers []config.ForbiddenMarke
119100
}
120101
}
121102

122-
// TODO: this should probably return some representation of the marker that is failing the
123-
// attribute rules so that it can be returned to users helpfully.
124-
func markerMatchesAttributeRules(marker markers.Marker, attrRules ...config.ForbiddenMarkerAttribute) bool {
103+
func markerMatchesAttributeRules(marker markers.Marker, attrRules ...MarkerAttribute) bool {
125104
matchesAll := true
126105
for _, attrRule := range attrRules {
127-
if val, ok := marker.Expressions[attrRule.Attribute]; ok {
106+
if val, ok := marker.Expressions[attrRule.Name]; ok {
128107
// if no values are specified, that means the existence match is enough
129108
// and we can continue to the next rule
130109
if len(attrRule.Values) == 0 {
@@ -144,6 +123,7 @@ func markerMatchesAttributeRules(marker markers.Marker, attrRules ...config.Forb
144123
matchesAll = false
145124
break
146125
}
126+
continue
147127
}
148128
// if the marker doesn't contain the attribute for a specified rule it fails the AND
149129
// operation.
@@ -158,14 +138,14 @@ func reportField(pass *analysis.Pass, field *ast.Field) func(marker markers.Mark
158138
return func(marker markers.Marker) {
159139
pass.Report(analysis.Diagnostic{
160140
Pos: field.Pos(),
161-
Message: fmt.Sprintf("field %s has forbidden marker %q", field.Names[0].Name, marker.Identifier),
141+
Message: fmt.Sprintf("field %s has forbidden marker %q", field.Names[0].Name, marker.String()),
162142
SuggestedFixes: []analysis.SuggestedFix{
163143
{
164-
Message: fmt.Sprintf("remove forbidden marker %q", marker.Identifier),
144+
Message: fmt.Sprintf("remove forbidden marker %q", marker.String()),
165145
TextEdits: []analysis.TextEdit{
166146
{
167147
Pos: marker.Pos,
168-
End: marker.End + 1,
148+
End: marker.End,
169149
},
170150
},
171151
},
@@ -178,7 +158,18 @@ func reportType(pass *analysis.Pass, typeSpec *ast.TypeSpec) func(marker markers
178158
return func(marker markers.Marker) {
179159
pass.Report(analysis.Diagnostic{
180160
Pos: typeSpec.Pos(),
181-
Message: fmt.Sprintf("type %s has forbidden marker %q", typeSpec.Name, marker.Identifier),
161+
Message: fmt.Sprintf("type %s has forbidden marker %q", typeSpec.Name, marker.String()),
162+
SuggestedFixes: []analysis.SuggestedFix{
163+
{
164+
Message: fmt.Sprintf("remove forbidden marker %q", marker.String()),
165+
TextEdits: []analysis.TextEdit{
166+
{
167+
Pos: marker.Pos,
168+
End: marker.End,
169+
},
170+
},
171+
},
172+
},
182173
})
183174
}
184175
}

pkg/analysis/forbiddenmarkers/analyzer_test.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,34 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
16-
package forbiddenmarkers_test
16+
package forbiddenmarkers
1717

1818
import (
1919
"testing"
2020

2121
"golang.org/x/tools/go/analysis/analysistest"
22-
"sigs.k8s.io/kube-api-linter/pkg/analysis/forbiddenmarkers"
23-
"sigs.k8s.io/kube-api-linter/pkg/config"
2422
)
2523

2624
func TestWithConfiguration(t *testing.T) {
2725
testdata := analysistest.TestData()
28-
analysistest.RunWithSuggestedFixes(t, testdata, forbiddenmarkers.NewAnalyzer(config.ForbiddenMarkersConfig{
29-
Markers: []config.ForbiddenMarker{
26+
analysistest.RunWithSuggestedFixes(t, testdata, newAnalyzer(&Config{
27+
Markers: []Marker{
3028
{
3129
Identifier: "custom:forbidden",
3230
},
3331
{
3432
Identifier: "custom:AttrNoValues",
35-
Attributes: []config.ForbiddenMarkerAttribute{
33+
Attributes: []MarkerAttribute{
3634
{
37-
Attribute: "fruit",
35+
Name: "fruit",
3836
},
3937
},
4038
},
4139
{
4240
Identifier: "custom:AttrValues",
43-
Attributes: []config.ForbiddenMarkerAttribute{
41+
Attributes: []MarkerAttribute{
4442
{
45-
Attribute: "fruit",
43+
Name: "fruit",
4644
Values: []string{
4745
"apple",
4846
"orange",
@@ -53,28 +51,28 @@ func TestWithConfiguration(t *testing.T) {
5351
},
5452
{
5553
Identifier: "custom:AttrsNoValues",
56-
Attributes: []config.ForbiddenMarkerAttribute{
54+
Attributes: []MarkerAttribute{
5755
{
58-
Attribute: "fruit",
56+
Name: "fruit",
5957
},
6058
{
61-
Attribute: "color",
59+
Name: "color",
6260
},
6361
},
6462
},
6563
{
6664
Identifier: "custom:AttrsValues",
67-
Attributes: []config.ForbiddenMarkerAttribute{
65+
Attributes: []MarkerAttribute{
6866
{
69-
Attribute: "fruit",
67+
Name: "fruit",
7068
Values: []string{
7169
"apple",
7270
"orange",
7371
"banana",
7472
},
7573
},
7674
{
77-
Attribute: "color",
75+
Name: "color",
7876
Values: []string{
7977
"red",
8078
"blue",
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package forbiddenmarkers
2+
3+
// Config is the configuration type
4+
// for the forbiddenmarkers linter.
5+
type Config struct {
6+
// markers is the unique set of markers
7+
// that are forbidden on types/fields.
8+
// Uniqueness is keyed on the `identifier`
9+
// field of entries.
10+
// Must have at least one entry.
11+
Markers []Marker `json:"markers"`
12+
}
13+
14+
// Marker is a representation of a
15+
// type/field marker that should be forbidden.
16+
type Marker struct {
17+
// identifier is the identifier for the forbidden marker.
18+
Identifier string `json:"identifier"`
19+
// attributes is an optional unique set of
20+
// attributes that is forbidden for this marker.
21+
// Uniqueness is keyed on the `name` field of entries.
22+
// When specified, only instances of this marker
23+
// that contains all the attributes will be considered
24+
// forbidden.
25+
// When not specified, any instance of the marker will be
26+
// considered forbidden.
27+
Attributes []MarkerAttribute `json:"attributes,omitempty"`
28+
}
29+
30+
// MarkerAttribute is a representation of an
31+
// attribute for a marker.
32+
type MarkerAttribute struct {
33+
// name is the name of the forbidden attribute
34+
Name string `json:"name"`
35+
// values is an optional unique set of
36+
// values that are forbidden for this marker.
37+
// When specified, only the instances of this
38+
// attribute that set one of these forbidden values
39+
// will be considered forbidden.
40+
// When not specified, any use of this attribute
41+
// will be considered forbidden.
42+
Values []string `json:"values,omitempty"`
43+
}

0 commit comments

Comments
 (0)