@@ -35,7 +35,7 @@ func (*StructTagRule) Name() string {
3535
3636type lintStructTagRule struct {
3737 onFailure func (lint.Failure )
38- usedTagNbr map [string ]bool // list of used tag numbers
38+ usedTagNbr map [int ]bool // list of used tag numbers
3939 usedTagName map [string ]bool // list of used tag keys
4040}
4141
@@ -45,7 +45,7 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
4545 if n .Fields == nil || n .Fields .NumFields () < 1 {
4646 return nil // skip empty structs
4747 }
48- w .usedTagNbr = map [string ]bool {} // init
48+ w .usedTagNbr = map [int ]bool {} // init
4949 w .usedTagName = map [string ]bool {} // init
5050 for _ , f := range n .Fields .List {
5151 if f .Tag != nil {
@@ -74,6 +74,10 @@ func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool)
7474 }
7575
7676 tagName := w .getTagName (tag )
77+ if tagName == "" {
78+ return "" , true // No tag name found
79+ }
80+
7781 // We concat the key and name as the mapping key here
7882 // to allow the same tag name in different tag type.
7983 key := tag .Key + ":" + tagName
@@ -94,7 +98,7 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string {
9498 return strings .TrimLeft (option , "name=" )
9599 }
96100 }
97- return "protobuf tag lacks name"
101+ return "" // protobuf tag lacks ' name' option
98102 default :
99103 return tag .Name
100104 }
@@ -139,7 +143,10 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
139143 w .addFailure (f .Tag , msg )
140144 }
141145 case "protobuf" :
142- // Not implemented yet
146+ msg , ok := w .checkProtobufTag (tag )
147+ if ! ok {
148+ w .addFailure (f .Tag , msg )
149+ }
143150 case "required" :
144151 if tag .Name != "true" && tag .Name != "false" {
145152 w .addFailure (f .Tag , "required should be 'true' or 'false'" )
@@ -170,10 +177,14 @@ func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string,
170177 if strings .HasPrefix (opt , "tag:" ) {
171178 parts := strings .Split (opt , ":" )
172179 tagNumber := parts [1 ]
173- if w .usedTagNbr [tagNumber ] {
174- return fmt .Sprintf ("duplicated tag number %s" , tagNumber ), false
180+ number , err := strconv .Atoi (tagNumber )
181+ if err != nil {
182+ return fmt .Sprintf ("ASN1 tag must be a number, got '%s'" , tagNumber ), false
183+ }
184+ if w .usedTagNbr [number ] {
185+ return fmt .Sprintf ("duplicated tag number %v" , number ), false
175186 }
176- w .usedTagNbr [tagNumber ] = true
187+ w .usedTagNbr [number ] = true
177188
178189 continue
179190 }
@@ -275,6 +286,57 @@ func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
275286 return typeMatches
276287}
277288
289+ func (w lintStructTagRule ) checkProtobufTag (tag * structtag.Tag ) (string , bool ) {
290+ // check name
291+ switch tag .Name {
292+ case "bytes" , "fixed32" , "fixed64" , "group" , "varint" , "zigzag32" , "zigzag64" :
293+ // do nothing
294+ default :
295+ return fmt .Sprintf ("invalid protobuf tag name '%s'" , tag .Name ), false
296+ }
297+
298+ // check options
299+ seenOptions := map [string ]bool {}
300+ for _ , opt := range tag .Options {
301+ if number , err := strconv .Atoi (opt ); err == nil {
302+ _ , alreadySeen := w .usedTagNbr [number ]
303+ if alreadySeen {
304+ return fmt .Sprintf ("duplicated tag number %v" , number ), false
305+ }
306+ w .usedTagNbr [number ] = true
307+ continue // option is an integer
308+ }
309+
310+ switch {
311+ case opt == "opt" || opt == "proto3" || opt == "rep" || opt == "req" :
312+ // do nothing
313+ case strings .Contains (opt , "=" ):
314+ o := strings .Split (opt , "=" )[0 ]
315+ _ , alreadySeen := seenOptions [o ]
316+ if alreadySeen {
317+ return fmt .Sprintf ("protobuf tag has duplicated option '%s'" , o ), false
318+ }
319+ seenOptions [o ] = true
320+ continue
321+ }
322+ }
323+ _ , hasName := seenOptions ["name" ]
324+ if ! hasName {
325+ return "protobuf tag lacks mandatory option 'name'" , false
326+ }
327+
328+ for k := range seenOptions {
329+ switch k {
330+ case "name" , "json" :
331+ // do nothing
332+ default :
333+ return fmt .Sprintf ("unknown option '%s' in protobuf tag" , k ), false
334+ }
335+ }
336+
337+ return "" , true
338+ }
339+
278340func (w lintStructTagRule ) addFailure (n ast.Node , msg string ) {
279341 w .onFailure (lint.Failure {
280342 Node : n ,
0 commit comments