Skip to content

Instantly share code, notes, and snippets.

@Miciah
Created November 26, 2021 18:21
Show Gist options
  • Save Miciah/8b99806c30cb819f97210d618c8ada8f to your computer and use it in GitHub Desktop.
Save Miciah/8b99806c30cb819f97210d618c8ada8f to your computer and use it in GitHub Desktop.
Check the godoc for struct fields in type definitions in Go source code files in the current working directory and report whether the godoc for each field refers to the field by identifier or by json tag
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"strconv"
"strings"
)
var (
fieldsWithGodocMatchingIdent int
fieldsWithGodocMatchingJson int
fieldsWithWhackadoodleGodoc int
)
func inspect(node ast.Node) bool {
t, ok := node.(*ast.TypeSpec)
if !ok {
return true
}
v, ok := t.Type.(*ast.StructType)
if !ok {
return true
}
for _, f := range v.Fields.List {
// Skip the field if it has no godoc.
if f.Doc == nil || len(f.Doc.List) == 0 {
continue
}
commentWords := strings.Split(f.Doc.List[0].Text, " ")
if len(commentWords) < 2 {
continue
}
// commentWords[0] is "//".
godocName := strings.TrimSuffix(commentWords[1], ",")
// Skip the field if it doesn't have a name.
// TODO Handle embedded fields.
if len(f.Names) == 0 {
continue
}
goIdent := f.Names[0].Name
// Skip the field if it doesn't have a tag.
if f.Tag == nil {
continue
}
tag, err := strconv.Unquote(f.Tag.Value)
if err != nil {
fmt.Println("failed to unquote tag:", err)
continue
}
tagWords := strings.Split(tag, " ")
var (
jsonTag string
foundJsonTag bool
)
for _, w := range tagWords {
kv := strings.Split(w, ":")
if len(kv) < 2 {
continue
}
key, quotedValue := kv[0], kv[1]
if key != "json" {
continue
}
value, err := strconv.Unquote(quotedValue)
if err != nil {
fmt.Println("failed to unquote tag value:", err)
break
}
jsonTag = strings.Split(value, ",")[0]
if jsonTag == "" {
break
}
foundJsonTag = true
}
if !foundJsonTag {
continue
}
var prefix string
if godocName == goIdent {
prefix = "✗"
fieldsWithGodocMatchingIdent++
} else if godocName == jsonTag {
prefix = "✓"
fieldsWithGodocMatchingJson++
} else {
prefix = "?"
fieldsWithWhackadoodleGodoc++
}
fmt.Printf("%s %s.%s has godoc name %q.\n", prefix, t.Name.Name, goIdent, godocName)
}
return true
}
func main() {
fset := token.NewFileSet()
packages, err := parser.ParseDir(fset, "./", nil, parser.ParseComments)
if err != nil {
panic(err)
}
for _, p := range packages {
for _, f := range p.Files {
ast.Inspect(f, inspect)
}
}
fmt.Println(fieldsWithGodocMatchingJson, "fields have godoc matching their json tags.")
fmt.Println(fieldsWithGodocMatchingIdent, "fields have godoc matching their Go idents.")
fmt.Println(fieldsWithWhackadoodleGodoc, "fields have godoc matching neither the ident nor the json tag.")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment