Skip to content

Commit d65d24a

Browse files
authored
Merge pull request #220 from itzPranshul/main-customMessages
Add support for custom messages in preferredmarkers linter
2 parents 470c3a3 + 78d6520 commit d65d24a

6 files changed

Lines changed: 52 additions & 7 deletions

File tree

pkg/analysis/preferredmarkers/analyzer.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,29 @@ const name = "preferredmarkers"
3434
type analyzer struct {
3535
// equivalentToPreferred maps equivalent marker identifiers to their preferred identifiers
3636
equivalentToPreferred map[string]string
37+
38+
// preferredToMessage maps preferred marker identifiers to their custom message
39+
preferredToMessage map[string]string
3740
}
3841

3942
// newAnalyzer creates a new analysis.Analyzer for the preferredmarkers
4043
// linter based on the provided Config.
4144
func newAnalyzer(cfg *Config) *analysis.Analyzer {
4245
a := &analyzer{
4346
equivalentToPreferred: make(map[string]string),
47+
preferredToMessage: make(map[string]string),
4448
}
4549

4650
// Build the mapping from equivalent identifiers to preferred identifiers
4751
for _, marker := range cfg.Markers {
4852
for _, equivalent := range marker.EquivalentIdentifiers {
4953
a.equivalentToPreferred[equivalent.Identifier] = marker.PreferredIdentifier
5054
}
55+
56+
msg := marker.Message
57+
if msg != "" {
58+
a.preferredToMessage[marker.PreferredIdentifier] = msg
59+
}
5160
}
5261

5362
analyzer := &analysis.Analyzer{
@@ -75,11 +84,11 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
7584
}
7685

7786
inspect.InspectFields(func(field *ast.Field, _ extractjsontags.FieldTagInfo, markersAccess markers.Markers, qualifiedFieldName string) {
78-
checkField(pass, field, markersAccess, a.equivalentToPreferred, qualifiedFieldName)
87+
checkField(pass, field, markersAccess, a.equivalentToPreferred, a.preferredToMessage, qualifiedFieldName)
7988
})
8089

8190
inspect.InspectTypeSpec(func(typeSpec *ast.TypeSpec, markersAccess markers.Markers) {
82-
checkType(pass, typeSpec, markersAccess, a.equivalentToPreferred)
91+
checkType(pass, typeSpec, markersAccess, a.equivalentToPreferred, a.preferredToMessage)
8392
})
8493

8594
return nil, nil //nolint:nilnil
@@ -88,26 +97,26 @@ func (a *analyzer) run(pass *analysis.Pass) (any, error) {
8897
// checkField validates a single struct field for marker usage.
8998
// Only checks markers directly on the field, not inherited from type aliases,
9099
// since inherited markers are already reported at the type level.
91-
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, equivalentToPreferred map[string]string, qualifiedFieldName string) {
100+
func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markers.Markers, equivalentToPreferred, preferredToMessage map[string]string, qualifiedFieldName string) {
92101
if field == nil || len(field.Names) == 0 {
93102
return
94103
}
95104

96105
markerSet := markersAccess.FieldMarkers(field)
97106
check(markerSet, equivalentToPreferred, func(marks []markers.Marker, preferredIdentifier string, preferredExists bool) {
98-
reportMarkers(pass, marks, preferredIdentifier, qualifiedFieldName, field.Pos(), "field", preferredExists)
107+
reportMarkers(pass, marks, preferredIdentifier, preferredToMessage[preferredIdentifier], qualifiedFieldName, field.Pos(), "field", preferredExists)
99108
})
100109
}
101110

102111
// checkType validates a single type definition for marker usage.
103-
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, equivalentToPreferred map[string]string) {
112+
func checkType(pass *analysis.Pass, typeSpec *ast.TypeSpec, markersAccess markers.Markers, equivalentToPreferred, preferredToMessage map[string]string) {
104113
if typeSpec == nil {
105114
return
106115
}
107116

108117
markerSet := markersAccess.TypeMarkers(typeSpec)
109118
check(markerSet, equivalentToPreferred, func(marks []markers.Marker, preferredIdentifier string, preferredExists bool) {
110-
reportMarkers(pass, marks, preferredIdentifier, typeSpec.Name.Name, typeSpec.Pos(), "type", preferredExists)
119+
reportMarkers(pass, marks, preferredIdentifier, preferredToMessage[preferredIdentifier], typeSpec.Name.Name, typeSpec.Pos(), "type", preferredExists)
111120
})
112121
}
113122

@@ -193,7 +202,7 @@ func buildTextEdits(marks []markers.Marker, preferredIdentifier string, preferre
193202
// reportMarkers generates a diagnostic report for markers that should be
194203
// replaced. This function handles the common logic for both field and type
195204
// reporting.
196-
func reportMarkers(pass *analysis.Pass, marks []markers.Marker, preferredIdentifier, elementName string, pos token.Pos, elementType string, preferredExists bool) {
205+
func reportMarkers(pass *analysis.Pass, marks []markers.Marker, preferredIdentifier, customMessage, elementName string, pos token.Pos, elementType string, preferredExists bool) {
197206
if len(marks) == 0 {
198207
return
199208
}
@@ -206,6 +215,10 @@ func reportMarkers(pass *analysis.Pass, marks []markers.Marker, preferredIdentif
206215
message := fmt.Sprintf("%s %s uses %s %s, should use preferred marker %q instead",
207216
elementType, elementName, markerWord, formatMarkerList(marks), preferredIdentifier)
208217

218+
if customMessage != "" {
219+
message += fmt.Sprintf(": %s", customMessage)
220+
}
221+
209222
fixMessage := "remove equivalent markers"
210223
if !preferredExists {
211224
fixMessage = fmt.Sprintf("replace with %q", preferredIdentifier)

pkg/analysis/preferredmarkers/analyzer_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,18 @@ func TestWithConfiguration(t *testing.T) {
4848
},
4949
}), "a/...")
5050
}
51+
52+
func TestWithCustomMessage(t *testing.T) {
53+
testdata := analysistest.TestData()
54+
analysistest.RunWithSuggestedFixes(t, testdata, newAnalyzer(&Config{
55+
Markers: []Marker{
56+
{
57+
PreferredIdentifier: "k8s:optional",
58+
EquivalentIdentifiers: []EquivalentIdentifier{
59+
{Identifier: "kubebuilder:validation:Optional"},
60+
},
61+
Message: "custom message for k8s:optional",
62+
},
63+
},
64+
}), "b/...")
65+
}

pkg/analysis/preferredmarkers/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type Marker struct {
3939
// preferred identifier.
4040
// Must have at least one entry.
4141
EquivalentIdentifiers []EquivalentIdentifier `json:"equivalentIdentifiers"`
42+
43+
// message is an explanation of why the preferred identifier should be used.
44+
// If not provided, a default message will be used.
45+
Message string `json:"message,omitempty"`
4246
}
4347

4448
// EquivalentIdentifier represents a marker identifier that should be

pkg/analysis/preferredmarkers/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ limitations under the License.
4343
* - preferredIdentifier: "k8s:optional"
4444
* equivalentIdentifiers:
4545
* - "kubebuilder:validation:Optional"
46+
* message: "Use k8s:optional to ensure consistency with upstream Kubernetes types."
4647
* ```
4748
*
4849
* **Scenario:** Enforce using a custom marker instead of multiple equivalent markers
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package b
2+
3+
type CustomMessage struct {
4+
// +kubebuilder:validation:Optional
5+
Field string // want "field CustomMessage.Field uses marker \"kubebuilder:validation:Optional\", should use preferred marker \"k8s:optional\" instead: custom message for k8s:optional"
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package b
2+
3+
type CustomMessage struct {
4+
// +k8s:optional
5+
Field string // want "field CustomMessage.Field uses marker \"kubebuilder:validation:Optional\", should use preferred marker \"k8s:optional\" instead: custom message for k8s:optional"
6+
}

0 commit comments

Comments
 (0)