github.com/alecthomas/jsonschema@v0.0.0-20220216202328-9eeeec9d044b/comment_extractor.go (about) 1 package jsonschema 2 3 import ( 4 "fmt" 5 "io/fs" 6 gopath "path" 7 "path/filepath" 8 "strings" 9 10 "go/ast" 11 "go/doc" 12 "go/parser" 13 "go/token" 14 ) 15 16 // ExtractGoComments will read all the go files contained in the provided path, 17 // including sub-directories, in order to generate a dictionary of comments 18 // associated with Types and Fields. The results will be added to the `commentsMap` 19 // provided in the parameters and expected to be used for Schema "description" fields. 20 // 21 // The `go/parser` library is used to extract all the comments and unfortunately doesn't 22 // have a built-in way to determine the fully qualified name of a package. The `base` paremeter, 23 // the URL used to import that package, is thus required to be able to match reflected types. 24 // 25 // When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase 26 // only. Field comments, which tend to be much shorter, will include everything. 27 func ExtractGoComments(base, path string, commentMap map[string]string) error { 28 fset := token.NewFileSet() 29 dict := make(map[string][]*ast.Package) 30 err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { 31 if err != nil { 32 return err 33 } 34 if info.IsDir() { 35 d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) 36 if err != nil { 37 return err 38 } 39 for _, v := range d { 40 // paths may have multiple packages, like for tests 41 k := gopath.Join(base, path) 42 dict[k] = append(dict[k], v) 43 } 44 } 45 return nil 46 }) 47 if err != nil { 48 return err 49 } 50 51 for pkg, p := range dict { 52 for _, f := range p { 53 gtxt := "" 54 typ := "" 55 ast.Inspect(f, func(n ast.Node) bool { 56 switch x := n.(type) { 57 case *ast.TypeSpec: 58 typ = x.Name.String() 59 if !ast.IsExported(typ) { 60 typ = "" 61 } else { 62 txt := x.Doc.Text() 63 if txt == "" && gtxt != "" { 64 txt = gtxt 65 gtxt = "" 66 } 67 txt = doc.Synopsis(txt) 68 commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) 69 } 70 case *ast.Field: 71 txt := x.Doc.Text() 72 if typ != "" && txt != "" { 73 for _, n := range x.Names { 74 if ast.IsExported(n.String()) { 75 k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) 76 commentMap[k] = strings.TrimSpace(txt) 77 } 78 } 79 } 80 case *ast.GenDecl: 81 // remember for the next type 82 gtxt = x.Doc.Text() 83 } 84 return true 85 }) 86 } 87 } 88 89 return nil 90 }