@@ -203,6 +203,14 @@ func validateMessage(res *result, isProto3 bool, name protoreflect.FullName, md
203
203
// or reserved ranges or reserved names
204
204
rsvdNames := map [string ]struct {}{}
205
205
for _ , n := range md .ReservedName {
206
+ // validate reserved name while we're here
207
+ if ! isIdentifier (n ) {
208
+ node := findMessageReservedNameNode (res .MessageNode (md ), n )
209
+ nodeInfo := res .file .NodeInfo (node )
210
+ if err := handler .HandleErrorf (nodeInfo .Start (), "%s: reserved name %q is not a valid identifier" , scope , n ); err != nil {
211
+ return err
212
+ }
213
+ }
206
214
rsvdNames [n ] = struct {}{}
207
215
}
208
216
fieldTags := map [int32 ]string {}
@@ -242,6 +250,60 @@ func validateMessage(res *result, isProto3 bool, name protoreflect.FullName, md
242
250
return nil
243
251
}
244
252
253
+ func isIdentifier (s string ) bool {
254
+ if len (s ) == 0 {
255
+ return false
256
+ }
257
+ for i , r := range s {
258
+ if i == 0 && r >= '0' && r <= '9' {
259
+ // can't start with number
260
+ return false
261
+ }
262
+ // alphanumeric and underscore ok; everything else bad
263
+ switch {
264
+ case r >= '0' && r <= '9' :
265
+ case r >= 'a' && r <= 'z' :
266
+ case r >= 'A' && r <= 'Z' :
267
+ case r == '_' :
268
+ default :
269
+ return false
270
+ }
271
+ }
272
+ return true
273
+ }
274
+
275
+ func findMessageReservedNameNode (msgNode ast.MessageDeclNode , name string ) ast.Node {
276
+ var decls []ast.MessageElement
277
+ switch msgNode := msgNode .(type ) {
278
+ case * ast.MessageNode :
279
+ decls = msgNode .Decls
280
+ case * ast.GroupNode :
281
+ decls = msgNode .Decls
282
+ default :
283
+ // leave decls empty
284
+ }
285
+ return findReservedNameNode (msgNode , decls , name )
286
+ }
287
+
288
+ func findReservedNameNode [T ast.Node ](parent ast.Node , decls []T , name string ) ast.Node {
289
+ for _ , decl := range decls {
290
+ // NB: We have to convert to empty interface first, before we can do a type
291
+ // assertion because type assertions on type parameters aren't allowed. (The
292
+ // compiler cannot yet know whether T is an interface type or not.)
293
+ rsvd , ok := any (decl ).(* ast.ReservedNode )
294
+ if ! ok {
295
+ continue
296
+ }
297
+ for _ , rsvdName := range rsvd .Names {
298
+ if rsvdName .AsString () == name {
299
+ return rsvdName
300
+ }
301
+ }
302
+ }
303
+ // couldn't find it? Instead of puking, report position of the parent.
304
+ return parent
305
+ }
306
+
245
307
func validateEnum (res * result , isProto3 bool , name protoreflect.FullName , ed * descriptorpb.EnumDescriptorProto , handler * reporter.Handler ) error {
246
308
scope := fmt .Sprintf ("enum %s" , name )
247
309
@@ -331,6 +393,14 @@ func validateEnum(res *result, isProto3 bool, name protoreflect.FullName, ed *de
331
393
// or reserved ranges or reserved names
332
394
rsvdNames := map [string ]struct {}{}
333
395
for _ , n := range ed .ReservedName {
396
+ // validate reserved name while we're here
397
+ if ! isIdentifier (n ) {
398
+ node := findEnumReservedNameNode (res .EnumNode (ed ), n )
399
+ nodeInfo := res .file .NodeInfo (node )
400
+ if err := handler .HandleErrorf (nodeInfo .Start (), "%s: reserved name %q is not a valid identifier" , scope , n ); err != nil {
401
+ return err
402
+ }
403
+ }
334
404
rsvdNames [n ] = struct {}{}
335
405
}
336
406
for _ , ev := range ed .Value {
@@ -354,6 +424,15 @@ func validateEnum(res *result, isProto3 bool, name protoreflect.FullName, ed *de
354
424
return nil
355
425
}
356
426
427
+ func findEnumReservedNameNode (enumNode ast.Node , name string ) ast.Node {
428
+ var decls []ast.EnumElement
429
+ if enumNode , ok := enumNode .(* ast.EnumNode ); ok {
430
+ decls = enumNode .Decls
431
+ // if not the right type, we leave decls empty
432
+ }
433
+ return findReservedNameNode (enumNode , decls , name )
434
+ }
435
+
357
436
func validateField (res * result , isProto3 bool , name protoreflect.FullName , fld * descriptorpb.FieldDescriptorProto , handler * reporter.Handler ) error {
358
437
scope := fmt .Sprintf ("field %s" , name )
359
438
0 commit comments