core/cmd/errorcodes/main.go

150 lines
2.9 KiB
Go

package main
import (
"errors"
"go/ast"
"go/constant"
"go/importer"
"go/parser"
"go/token"
"go/types"
"io/fs"
"log"
"os"
"strings"
"gopkg.in/yaml.v3"
)
func main() {
if _, err := os.Stat("./go.mod"); errors.Is(err, os.ErrNotExist) {
log.Fatalln("go.mod not found")
}
if err := generateYAML("./internal/domain"); err != nil {
log.Fatalln(err)
}
}
func generateYAML(root string) error {
m, err := getErrorCodesFromFolder(root)
if err != nil {
return err
}
var description strings.Builder
enum := make([]string, 0, len(m))
for c, d := range m {
if d != "" {
if description.Len() > 0 {
description.WriteRune('\n')
}
description.WriteString("* `")
description.WriteString(c)
description.WriteString("` - ")
description.WriteString(d)
}
enum = append(enum, c)
}
return yaml.NewEncoder(os.Stdout).Encode(struct {
Type string `yaml:"type"`
Description string `yaml:"description"`
Enum []string `yaml:"enum"`
}{
Type: "string",
Description: description.String(),
Enum: enum,
})
}
//nolint:gocyclo
func getErrorCodesFromFolder(root string) (map[string]string, error) {
fset := token.NewFileSet()
astDomainPkg, err := parseDomainPackage(fset, root)
if err != nil {
return nil, err
}
conf := types.Config{Importer: importer.Default()}
files := make([]*ast.File, 0, len(astDomainPkg.Files))
for _, f := range astDomainPkg.Files {
files = append(files, f)
}
domainPkg, err := conf.Check(astDomainPkg.Name, fset, files, nil)
if err != nil {
return nil, err
}
scope := domainPkg.Scope()
m := make(map[string]string)
for _, n := range scope.Names() {
if !strings.HasPrefix(n, "errorCode") {
continue
}
obj := scope.Lookup(n)
c, ok := obj.(*types.Const)
if !ok || c.Val().Kind() != constant.String {
continue
}
f := astDomainPkg.Files[fset.File(c.Pos()).Name()]
position := fset.Position(c.Pos())
commentMap := ast.NewCommentMap(fset, f, f.Comments)
var desc string
for _, decl := range f.Decls {
declPosition := fset.Position(decl.Pos())
if declPosition.Filename != position.Filename || declPosition.Line != position.Line {
continue
}
if comments := commentMap.Filter(decl).Comments(); len(comments) > 0 {
desc = strings.TrimSpace(comments[0].Text())
}
break
}
if strings.Contains(desc, "errorcode:ignore") {
continue
}
m[constant.StringVal(c.Val())] = desc
}
return m, nil
}
func parseDomainPackage(fset *token.FileSet, root string) (*ast.Package, error) {
pkgs, err := parser.ParseDir(fset, root, func(info fs.FileInfo) bool {
if info.IsDir() && info.Name() != root {
return false
}
if info.IsDir() || strings.HasSuffix(info.Name(), "_test.go") {
return false
}
return true
}, parser.AllErrors|parser.ParseComments)
if err != nil {
return nil, err
}
astDomainPkg, ok := pkgs["domain"]
if !ok {
return nil, errors.New("domain pkg not found")
}
return astDomainPkg, nil
}