package main import ( "fmt" "go/ast" "go/parser" "go/token" "regexp" ) var decorComment = regexp.MustCompile(`^//\s?kittenipc:api$`) type apiStruct struct { pkgName string name string methods []*ast.FuncDecl } type GoApiParser struct { } func (g *GoApiParser) Parse(sourceFile string) (Api, error) { var apiStructs []*apiStruct fileSet := token.NewFileSet() astFile, err := parser.ParseFile(fileSet, sourceFile, nil, parser.ParseComments|parser.SkipObjectResolution) if err != nil { return Api{}, fmt.Errorf("parse file: %w", err) } pkgName := astFile.Name.Name for _, decl := range astFile.Decls { genDecl, ok := decl.(*ast.GenDecl) if !ok { continue } if genDecl.Doc == nil { continue } // use only last comment. https://tip.golang.org/doc/comment#syntax lastComment := genDecl.Doc.List[len(genDecl.Doc.List)-1] if !decorComment.MatchString(lastComment.Text) { continue } typeSpec, ok := genDecl.Specs[0].(*ast.TypeSpec) if !ok { continue } structType, ok := typeSpec.Type.(*ast.StructType) if !ok { continue } _ = structType apiStructs = append(apiStructs, &apiStruct{ name: typeSpec.Name.Name, pkgName: pkgName, }) } if len(apiStructs) == 0 { return Api{}, fmt.Errorf("no api struct found") } if len(apiStructs) > 1 { return Api{}, fmt.Errorf("multiple api struct found") } for _, decl := range astFile.Decls { funcDecl, ok := decl.(*ast.FuncDecl) if !ok { continue } if !funcDecl.Name.IsExported() { continue } if funcDecl.Recv == nil { continue } reciever := funcDecl.Recv.List[0] recvType := reciever.Type star, isPointer := recvType.(*ast.StarExpr) if isPointer { recvType = star.X } recvIdent, ok := recvType.(*ast.Ident) if !ok { continue } for _, apiStrct := range apiStructs { if recvIdent.Name == apiStrct.name && pkgName == apiStrct.pkgName { apiStrct.methods = append(apiStrct.methods, funcDecl) } } } return Api{}, nil }