Skip to content

Commit 2fd6632

Browse files
committed
Allow disabling fix for required fields pointer
1 parent 78c76ec commit 2fd6632

File tree

8 files changed

+202
-17
lines changed

8 files changed

+202
-17
lines changed

pkg/analysis/requiredfields/analyzer.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/JoelSpeed/kal/pkg/analysis/helpers/extractjsontags"
1010
"github.com/JoelSpeed/kal/pkg/analysis/helpers/markers"
11+
"github.com/JoelSpeed/kal/pkg/config"
1112
"golang.org/x/tools/go/analysis"
1213
"golang.org/x/tools/go/analysis/passes/inspect"
1314
"golang.org/x/tools/go/ast/inspector"
@@ -26,11 +27,17 @@ var (
2627
errCouldNotGetJSONTags = errors.New("could not get json tags")
2728
)
2829

29-
type analyzer struct{}
30+
type analyzer struct {
31+
pointerPolicy config.RequiredFieldPointerPolicy
32+
}
3033

3134
// newAnalyzer creates a new analyzer.
32-
func newAnalyzer() *analysis.Analyzer {
33-
a := &analyzer{}
35+
func newAnalyzer(cfg config.RequiredFieldsConfig) *analysis.Analyzer {
36+
defaultConfig(&cfg)
37+
38+
a := &analyzer{
39+
pointerPolicy: cfg.PointerPolicy,
40+
}
3441

3542
return &analysis.Analyzer{
3643
Name: name,
@@ -124,21 +131,34 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, fieldMarker
124131
}
125132

126133
if starExpr, ok := field.Type.(*ast.StarExpr); ok {
127-
pass.Report(analysis.Diagnostic{
128-
Pos: field.Pos(),
129-
Message: fmt.Sprintf("field %s is marked as required, should not be a pointer", fieldName),
130-
SuggestedFixes: []analysis.SuggestedFix{
131-
{
132-
Message: "should remove the pointer",
133-
TextEdits: []analysis.TextEdit{
134-
{
135-
Pos: starExpr.Pos(),
136-
End: starExpr.X.Pos(),
137-
NewText: nil,
138-
},
134+
var suggestedFixes []analysis.SuggestedFix
135+
136+
switch a.pointerPolicy {
137+
case config.RequiredFieldPointerWarn:
138+
// Do not suggest a fix.
139+
case config.RequiredFieldPointerSuggestFix:
140+
suggestedFixes = append(suggestedFixes, analysis.SuggestedFix{
141+
Message: "should remove the pointer",
142+
TextEdits: []analysis.TextEdit{
143+
{
144+
Pos: starExpr.Pos(),
145+
End: starExpr.X.Pos(),
146+
NewText: nil,
139147
},
140148
},
141-
},
149+
})
150+
}
151+
152+
pass.Report(analysis.Diagnostic{
153+
Pos: field.Pos(),
154+
Message: fmt.Sprintf("field %s is marked as required, should not be a pointer", fieldName),
155+
SuggestedFixes: suggestedFixes,
142156
})
143157
}
144158
}
159+
160+
func defaultConfig(cfg *config.RequiredFieldsConfig) {
161+
if cfg.PointerPolicy == "" {
162+
cfg.PointerPolicy = config.RequiredFieldPointerSuggestFix
163+
}
164+
}

pkg/analysis/requiredfields/analyzer_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,18 @@ func TestDefaultConfiguration(t *testing.T) {
1818

1919
analysistest.RunWithSuggestedFixes(t, testdata, a, "a")
2020
}
21+
22+
func TestWithPointerPolicyWarn(t *testing.T) {
23+
testdata := analysistest.TestData()
24+
25+
a, err := requiredfields.Initializer().Init(config.LintersConfig{
26+
RequiredFields: config.RequiredFieldsConfig{
27+
PointerPolicy: config.RequiredFieldPointerWarn,
28+
},
29+
})
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
analysistest.RunWithSuggestedFixes(t, testdata, a, "b")
35+
}

pkg/analysis/requiredfields/initializer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ func (initializer) Name() string {
2121

2222
// Init returns the intialized Analyzer.
2323
func (initializer) Init(cfg config.LintersConfig) (*analysis.Analyzer, error) {
24-
return newAnalyzer(), nil
24+
return newAnalyzer(cfg.RequiredFields), nil
2525
}
2626

2727
// Default determines whether this Analyzer is on by default, or not.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package a
2+
3+
type A struct {
4+
// optional field should not be picked up.
5+
// +optional
6+
OptionalField *string `json:"optionalField,omitempty"`
7+
8+
// requiredCorrectField should not be picked up.
9+
// +required
10+
RequiredCorrectField string `json:"requiredCorrectField"`
11+
12+
// requiredOmitEmptyField field should be picked up.
13+
// +required
14+
RequiredOmitEmptyField string `json:"requiredOmitEmptyField,omitempty"` // want "field RequiredOmitEmptyField is marked as required, but has the omitempty tag"
15+
16+
// requiredPointerField should be picked up.
17+
// +required
18+
RequiredPointerField *string `json:"requiredPointerField"` // want "field RequiredPointerField is marked as required, should not be a pointer"
19+
20+
// requiredPointerOmitEmptyField should be picked up.
21+
// +required
22+
RequiredPointerOmitEmptyField *string `json:"requiredPointerOmitEmptyField,omitempty"` // want "field RequiredPointerOmitEmptyField is marked as required, but has the omitempty tag" "field RequiredPointerOmitEmptyField is marked as required, should not be a pointer"
23+
24+
// requiredKubebuilderMarkerField should not be picked up.
25+
// +kubebuilder:validation:Required
26+
RequiredKubebuilderMarkerField string `json:"requiredKubebuilderMarkerField"`
27+
28+
// requiredKubebuilderMarkerOmitEmptyField should be picked up.
29+
// +kubebuilder:validation:Required
30+
RequiredKubebuilderMarkerOmitEmptyField string `json:"requiredKubebuilderMarkerOmitEmptyField,omitempty"` // want "field RequiredKubebuilderMarkerOmitEmptyField is marked as required, but has the omitempty tag"
31+
32+
// requiredKubebuilderMarkerPointerField should be picked up.
33+
// +kubebuilder:validation:Required
34+
RequiredKubebuilderMarkerPointerField *string `json:"requiredKubebuilderMarkerPointerField"` // want "field RequiredKubebuilderMarkerPointerField is marked as required, should not be a pointer"
35+
36+
// requiredKubebuilderMarkerPointerOmitEmptyField should be picked up.
37+
// +kubebuilder:validation:Required
38+
RequiredKubebuilderMarkerPointerOmitEmptyField *string `json:"requiredKubebuilderMarkerPointerOmitEmptyField,omitempty"` // want "field RequiredKubebuilderMarkerPointerOmitEmptyField is marked as required, but has the omitempty tag" "field RequiredKubebuilderMarkerPointerOmitEmptyField is marked as required, should not be a pointer"
39+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package a
2+
3+
type A struct {
4+
// optional field should not be picked up.
5+
// +optional
6+
OptionalField *string `json:"optionalField,omitempty"`
7+
8+
// requiredCorrectField should not be picked up.
9+
// +required
10+
RequiredCorrectField string `json:"requiredCorrectField"`
11+
12+
// requiredOmitEmptyField field should be picked up.
13+
// +required
14+
RequiredOmitEmptyField string `json:"requiredOmitEmptyField"` // want "field RequiredOmitEmptyField is marked as required, but has the omitempty tag"
15+
16+
// requiredPointerField should be picked up.
17+
// +required
18+
RequiredPointerField *string `json:"requiredPointerField"` // want "field RequiredPointerField is marked as required, should not be a pointer"
19+
20+
// requiredPointerOmitEmptyField should be picked up.
21+
// +required
22+
RequiredPointerOmitEmptyField *string `json:"requiredPointerOmitEmptyField"` // want "field RequiredPointerOmitEmptyField is marked as required, but has the omitempty tag" "field RequiredPointerOmitEmptyField is marked as required, should not be a pointer"
23+
24+
// requiredKubebuilderMarkerField should not be picked up.
25+
// +kubebuilder:validation:Required
26+
RequiredKubebuilderMarkerField string `json:"requiredKubebuilderMarkerField"`
27+
28+
// requiredKubebuilderMarkerOmitEmptyField should be picked up.
29+
// +kubebuilder:validation:Required
30+
RequiredKubebuilderMarkerOmitEmptyField string `json:"requiredKubebuilderMarkerOmitEmptyField"` // want "field RequiredKubebuilderMarkerOmitEmptyField is marked as required, but has the omitempty tag"
31+
32+
// requiredKubebuilderMarkerPointerField should be picked up.
33+
// +kubebuilder:validation:Required
34+
RequiredKubebuilderMarkerPointerField *string `json:"requiredKubebuilderMarkerPointerField"` // want "field RequiredKubebuilderMarkerPointerField is marked as required, should not be a pointer"
35+
36+
// requiredKubebuilderMarkerPointerOmitEmptyField should be picked up.
37+
// +kubebuilder:validation:Required
38+
RequiredKubebuilderMarkerPointerOmitEmptyField *string `json:"requiredKubebuilderMarkerPointerOmitEmptyField"` // want "field RequiredKubebuilderMarkerPointerOmitEmptyField is marked as required, but has the omitempty tag" "field RequiredKubebuilderMarkerPointerOmitEmptyField is marked as required, should not be a pointer"
39+
}

pkg/config/linters_config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ type LintersConfig struct {
77

88
// optionalOrRequired contains configuration for the optionalorrequired linter.
99
OptionalOrRequired OptionalOrRequiredConfig `json:"optionalOrRequired"`
10+
11+
// requiredFields contains configuration for the requiredfields linter.
12+
RequiredFields RequiredFieldsConfig `json:"requiredFields"`
1013
}
1114

1215
// JSONTagsConfig contains configuration for the jsontags linter.
@@ -29,3 +32,24 @@ type OptionalOrRequiredConfig struct {
2932
// Valid values are "required" and "kubebuilder:validation:Required".
3033
PreferredRequiredMarker string `json:"preferredRequiredMarker"`
3134
}
35+
36+
// RequiredFieldPointerPolicy is the policy for pointers in required fields.
37+
type RequiredFieldPointerPolicy string
38+
39+
const (
40+
// RequiredFieldPointerWarn indicates that the linter will emit a warning if a required field is a pointer.
41+
RequiredFieldPointerWarn RequiredFieldPointerPolicy = "Warn"
42+
43+
// RequiredFieldPointerSuggestFix indicates that the linter will emit a warning if a required field is a pointer and suggest a fix.
44+
RequiredFieldPointerSuggestFix RequiredFieldPointerPolicy = "SuggestFix"
45+
)
46+
47+
// RequiredFieldsConfig contains configuration for the requiredfields linter.
48+
type RequiredFieldsConfig struct {
49+
// pointerPolicy is the policy for pointers in required fields.
50+
// Valid values are "Warn" and "SuggestFix".
51+
// When set to "Warn", the linter will emit a warning if a required field is a pointer.
52+
// When set to "SuggestFix", the linter will emit a warning if a required field is a pointer and suggest a fix.
53+
// When otherwise not specified, the default value is "SuggestFix".
54+
PointerPolicy RequiredFieldPointerPolicy `json:"pointerPolicy"`
55+
}

pkg/validation/linters_config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func ValidateLintersConfig(lc config.LintersConfig, fldPath *field.Path) field.E
1616

1717
fieldErrors = append(fieldErrors, validateJSONTagsConfig(lc.JSONTags, fldPath.Child("jsonTags"))...)
1818
fieldErrors = append(fieldErrors, validateOptionalOrRequiredConfig(lc.OptionalOrRequired, fldPath.Child("optionalOrRequired"))...)
19+
fieldErrors = append(fieldErrors, validateRequiredFieldsConfig(lc.RequiredFields, fldPath.Child("requiredFields"))...)
1920

2021
return fieldErrors
2122
}
@@ -51,3 +52,16 @@ func validateOptionalOrRequiredConfig(oorc config.OptionalOrRequiredConfig, fldP
5152

5253
return fieldErrors
5354
}
55+
56+
// validateRequiredFieldsConfig is used to validate the configuration in the config.RequiredFieldsConfig struct.
57+
func validateRequiredFieldsConfig(rfc config.RequiredFieldsConfig, fldPath *field.Path) field.ErrorList {
58+
fieldErrors := field.ErrorList{}
59+
60+
switch rfc.PointerPolicy {
61+
case "", config.RequiredFieldPointerWarn, config.RequiredFieldPointerSuggestFix:
62+
default:
63+
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("pointerPolicy"), rfc.PointerPolicy, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", config.RequiredFieldPointerWarn, config.RequiredFieldPointerSuggestFix)))
64+
}
65+
66+
return fieldErrors
67+
}

pkg/validation/linters_config_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,5 +83,39 @@ var _ = Describe("LintersConfig", func() {
8383
},
8484
expectedErr: "lintersConfig.optionalOrRequired.preferredRequiredMarker: Invalid value: \"invalid\": invalid value, must be one of \"required\", \"kubebuilder:validation:Required\" or omitted",
8585
}),
86+
87+
// RequiredFieldsConfig validation
88+
Entry("With a valid RequiredFieldsConfig: omitted", validateLintersConfigTableInput{
89+
config: config.LintersConfig{
90+
RequiredFields: config.RequiredFieldsConfig{
91+
PointerPolicy: "",
92+
},
93+
},
94+
expectedErr: "",
95+
}),
96+
Entry("With a valid RequiredFieldsConfig: SuggestFix", validateLintersConfigTableInput{
97+
config: config.LintersConfig{
98+
RequiredFields: config.RequiredFieldsConfig{
99+
PointerPolicy: config.RequiredFieldPointerSuggestFix,
100+
},
101+
},
102+
expectedErr: "",
103+
}),
104+
Entry("With a valid RequiredFieldsConfig: Warn", validateLintersConfigTableInput{
105+
config: config.LintersConfig{
106+
RequiredFields: config.RequiredFieldsConfig{
107+
PointerPolicy: config.RequiredFieldPointerWarn,
108+
},
109+
},
110+
expectedErr: "",
111+
}),
112+
Entry("With an invalid RequiredFieldsConfig", validateLintersConfigTableInput{
113+
config: config.LintersConfig{
114+
RequiredFields: config.RequiredFieldsConfig{
115+
PointerPolicy: "invalid",
116+
},
117+
},
118+
expectedErr: "lintersConfig.requiredFields.pointerPolicy: Invalid value: \"invalid\": invalid value, must be one of \"Warn\", \"SuggestFix\" or omitted",
119+
}),
86120
)
87121
})

0 commit comments

Comments
 (0)