Skip to content

Commit 64417f6

Browse files
authored
Merge pull request #62 from sivchari/optionalorrequired-diagnostic
check if type spec has optional or required marker
2 parents 0000638 + bdf208e commit 64417f6

File tree

7 files changed

+115
-0
lines changed

7 files changed

+115
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ It also supports the `// +kubebuilder:validation:Optional` and `// +kubebuilder:
240240

241241
If you prefer to use the Kubebuilder markers instead, you can change the preference in the configuration.
242242

243+
The `optionalorrequired` linter also checks for the presence of optional or required markers on type declarations, and forbids this pattern.
244+
243245
### Configuration
244246

245247
```yaml

pkg/analysis/helpers/inspector/inspector.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
type Inspector interface {
2929
// InspectFields is a function that iterates over fields in structs.
3030
InspectFields(func(field *ast.Field, stack []ast.Node, jsonTagInfo extractjsontags.FieldTagInfo, markersAccess markers.Markers))
31+
32+
// InspectTypeSpec is a function that inspects the type spec and calls the provided inspectTypeSpec function.
33+
InspectTypeSpec(func(typeSpec *ast.TypeSpec, markersAccess markers.Markers))
3134
}
3235

3336
// inspector implements the Inspector interface.
@@ -100,3 +103,19 @@ func (i *inspector) InspectFields(inspectField func(field *ast.Field, stack []as
100103
return true
101104
})
102105
}
106+
107+
// InspectTypeSpec inspects the type spec and calls the provided inspectTypeSpec function.
108+
func (i *inspector) InspectTypeSpec(inspectTypeSpec func(typeSpec *ast.TypeSpec, markersAccess markers.Markers)) {
109+
nodeFilter := []ast.Node{
110+
(*ast.TypeSpec)(nil),
111+
}
112+
113+
i.inspector.Preorder(nodeFilter, func(n ast.Node) {
114+
typeSpec, ok := n.(*ast.TypeSpec)
115+
if !ok {
116+
return
117+
}
118+
119+
inspectTypeSpec(typeSpec, i.markers)
120+
})
121+
}

pkg/analysis/optionalorrequired/analyzer.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ func (a *analyzer) run(pass *analysis.Pass) (interface{}, error) {
102102
a.checkField(pass, field, markersAccess.FieldMarkers(field), jsonTagInfo)
103103
})
104104

105+
inspect.InspectTypeSpec(func(typeSpec *ast.TypeSpec, markersAccess markers.Markers) {
106+
a.checkTypeSpec(pass, typeSpec, markersAccess)
107+
})
108+
105109
return nil, nil //nolint:nilnil
106110
}
107111

@@ -211,6 +215,33 @@ func reportShouldRemoveSecondaryMarker(field *ast.Field, marker []markers.Marker
211215
}
212216
}
213217

218+
func (a *analyzer) checkTypeSpec(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers) {
219+
name := typeSpec.Name.Name
220+
set := markersAccess.TypeMarkers(typeSpec)
221+
222+
for _, marker := range set.UnsortedList() {
223+
switch marker.Identifier {
224+
case a.primaryOptionalMarker, a.secondaryOptionalMarker, a.primaryRequiredMarker, a.secondaryRequiredMarker:
225+
pass.Report(analysis.Diagnostic{
226+
Pos: typeSpec.Pos(),
227+
Message: fmt.Sprintf("type %s should not be marked as %s", name, marker.String()),
228+
SuggestedFixes: []analysis.SuggestedFix{
229+
{
230+
Message: fmt.Sprintf("should remove `// +%s`", marker.String()),
231+
TextEdits: []analysis.TextEdit{
232+
{
233+
Pos: marker.Pos,
234+
End: marker.End,
235+
NewText: nil,
236+
},
237+
},
238+
},
239+
},
240+
})
241+
}
242+
}
243+
}
244+
214245
func defaultConfig(cfg *config.OptionalOrRequiredConfig) {
215246
if cfg.PreferredOptionalMarker == "" {
216247
cfg.PreferredOptionalMarker = OptionalMarker

pkg/analysis/optionalorrequired/analyzer_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,14 @@ func TestSwappedMarkerPriority(t *testing.T) {
4949

5050
analysistest.RunWithSuggestedFixes(t, testdata, a, "b")
5151
}
52+
53+
func TestTypeSpec(t *testing.T) {
54+
testdata := analysistest.TestData()
55+
56+
a, err := optionalorrequired.Initializer().Init(config.LintersConfig{})
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
61+
analysistest.RunWithSuggestedFixes(t, testdata, a, "c")
62+
}

pkg/analysis/optionalorrequired/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616

1717
/*
1818
optionalorrequired is a linter to ensure that all fields are marked as either optional or required.
19+
It also checks for the presence of optional or required markers on type declarations, and forbids this pattern.
20+
1921
By default, it searches for the `+optional` and `+required` markers, and ensures that all fields are marked
2022
with at least one of these markers.
2123
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package c
2+
3+
type OptionalOrRequiredTestStruct struct {
4+
RequiredEnumField RequiredEnum // want "field RequiredEnumField must be marked as optional or required"
5+
6+
KubebuilderRequiredEnumField KubeBuilderRequiredEnum // want "field KubebuilderRequiredEnumField must be marked as optional or required"
7+
8+
OptionalEnumField OptionalEnum // want "field OptionalEnumField must be marked as optional or required"
9+
10+
KubebuilderOptionalEnumField KubeBuilderOptionalEnum // want "field KubebuilderOptionalEnumField must be marked as optional or required"
11+
}
12+
13+
// +required
14+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
15+
type RequiredEnum string // want "type RequiredEnum should not be marked as required"
16+
17+
// +kubebuilder:validation:Required
18+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
19+
type KubeBuilderRequiredEnum string // want "type KubeBuilderRequiredEnum should not be marked as kubebuilder:validation:Required"
20+
21+
// +optional
22+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
23+
type OptionalEnum string // want "type OptionalEnum should not be marked as optional"
24+
25+
// +kubebuilder:validation:Optional
26+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
27+
type KubeBuilderOptionalEnum string // want "type KubeBuilderOptionalEnum should not be marked as kubebuilder:validation:Optional"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package c
2+
3+
type OptionalOrRequiredTestStruct struct {
4+
RequiredEnumField RequiredEnum // want "field RequiredEnumField must be marked as optional or required"
5+
6+
KubebuilderRequiredEnumField KubeBuilderRequiredEnum // want "field KubebuilderRequiredEnumField must be marked as optional or required"
7+
8+
OptionalEnumField OptionalEnum // want "field OptionalEnumField must be marked as optional or required"
9+
10+
KubebuilderOptionalEnumField KubeBuilderOptionalEnum // want "field KubebuilderOptionalEnumField must be marked as optional or required"
11+
}
12+
13+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
14+
type RequiredEnum string // want "type RequiredEnum should not be marked as required"
15+
16+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
17+
type KubeBuilderRequiredEnum string // want "type KubeBuilderRequiredEnum should not be marked as kubebuilder:validation:Required"
18+
19+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
20+
type OptionalEnum string // want "type OptionalEnum should not be marked as optional"
21+
22+
// +kubebuilder:validation:Enum=Foo;Bar;Baz
23+
type KubeBuilderOptionalEnum string // want "type KubeBuilderOptionalEnum should not be marked as kubebuilder:validation:Optional"

0 commit comments

Comments
 (0)