Skip to content

Commit e9025cb

Browse files
committed
markers: fix a bug when parsing expressions with commas present in values
Signed-off-by: Bryce Palmer <bpalmer@redhat.com>
1 parent bce00ef commit e9025cb

File tree

2 files changed

+65
-12
lines changed

2 files changed

+65
-12
lines changed

pkg/analysis/helpers/markers/analyzer.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"go/ast"
2020
"go/token"
2121
"reflect"
22+
"regexp"
2223
"strings"
2324

2425
"golang.org/x/tools/go/analysis"
@@ -229,24 +230,24 @@ func extractKnownMarkerIDAndExpressions(id string, marker string) (string, map[s
229230
return id, extractExpressions(strings.TrimPrefix(marker, id))
230231
}
231232

232-
func extractExpressions(expressions string) map[string]string {
233+
var expressionRegex = regexp.MustCompile(`\w*=(?:'[^']*'|"(\\"|[^"])*"|[\w;\-"]+)`)
234+
235+
func extractExpressions(expressionStr string) map[string]string {
233236
expressionsMap := map[string]string{}
234237

235238
// Do some normalization work to ensure we can parse expressions in
236239
// a standard way. Trim any lingering colons (:) and replace all ':='s with '='
237-
expressions = strings.TrimPrefix(expressions, ":")
238-
expressions = strings.ReplaceAll(expressions, ":=", "=")
239-
240-
// split expression string on commas (,) to handle multiple expressions
241-
// in a single marker
242-
chainedExpressions := strings.SplitSeq(expressions, ",")
243-
for chainedExpression := range chainedExpressions {
244-
exps := strings.SplitN(chainedExpression, "=", 2)
245-
if len(exps) < 2 {
240+
expressionStr = strings.TrimPrefix(expressionStr, ":")
241+
expressionStr = strings.ReplaceAll(expressionStr, ":=", "=")
242+
243+
expressions := expressionRegex.FindAllString(expressionStr, -1)
244+
for _, expression := range expressions {
245+
key, value, ok := strings.Cut(expression, "=")
246+
if !ok {
246247
continue
247248
}
248249

249-
expressionsMap[exps[0]] = exps[1]
250+
expressionsMap[key] = value
250251
}
251252

252253
return expressionsMap

pkg/analysis/helpers/markers/analyzer_test.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,66 @@ func TestExtractMarkerIdAndExpressions(t *testing.T) {
6969
"": "\"foo\"",
7070
},
7171
},
72+
{
73+
name: "registered marker with expression with a comma in its value",
74+
marker: `kubebuilder:validation:XValidation:rule='self.map(a, a == "someValue")',message='must have field!'`,
75+
expectedID: "kubebuilder:validation:XValidation",
76+
expectedExpressions: map[string]string{
77+
"rule": `'self.map(a, a == "someValue")'`,
78+
"message": "'must have field!'",
79+
},
80+
},
81+
{
82+
name: "registered marker with expression with a comma in its value with double quotes",
83+
marker: `kubebuilder:validation:XValidation:rule="self.map(a, a == \"someValue\")",message="must have field!"`,
84+
expectedID: "kubebuilder:validation:XValidation",
85+
expectedExpressions: map[string]string{
86+
"rule": `"self.map(a, a == \"someValue\")"`,
87+
"message": `"must have field!"`,
88+
},
89+
},
90+
{
91+
name: "registered marker with expression ending in a valid double quote",
92+
marker: `kubebuilder:validation:Enum:=foo;bar;baz;""`,
93+
expectedID: "kubebuilder:validation:Enum",
94+
expectedExpressions: map[string]string{
95+
"": `foo;bar;baz;""`,
96+
},
97+
},
98+
{
99+
name: "registered marker with chained expressions without quotes",
100+
marker: `custom:marker:fruit=apple,color=blue,country=UK`,
101+
expectedID: "custom:marker",
102+
expectedExpressions: map[string]string{
103+
"fruit": "apple",
104+
"color": "blue",
105+
"country": "UK",
106+
},
107+
},
108+
{
109+
name: "registered marker with numeric value",
110+
marker: `kubebuilder:validation:Minimum=10`,
111+
expectedID: "kubebuilder:validation:Minimum",
112+
expectedExpressions: map[string]string{
113+
"": "10",
114+
},
115+
},
116+
{
117+
name: "registered marker with negative numeric value",
118+
marker: `kubebuilder:validation:Minimum=-10`,
119+
expectedID: "kubebuilder:validation:Minimum",
120+
expectedExpressions: map[string]string{
121+
"": "-10",
122+
},
123+
},
72124
}
73125

74126
for _, tc := range testcases {
75127
t.Run(tc.name, func(t *testing.T) {
76128
g := NewWithT(t)
77129

78130
reg := NewRegistry()
79-
reg.Register("kubebuilder:object:root", "required", "kubebuilder:validation:XValidation")
131+
reg.Register(tc.expectedID)
80132

81133
id, expressions := extractMarkerIDAndExpressions(reg, tc.marker)
82134

0 commit comments

Comments
 (0)