123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- package goparser
- import (
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "log"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "runtime"
- "strings"
- "go-common/app/tool/warden/types"
- )
- var protoFileRegexp *regexp.Regexp
- const (
- optionsPrefix = "+wd:"
- )
- func init() {
- protoFileRegexp = regexp.MustCompile(`//\s+source:\s+(.*\.proto)`)
- }
- // GoPackage get go package name from file or directory path
- func GoPackage(dpath string) (string, error) {
- if strings.HasSuffix(dpath, ".go") {
- dpath = filepath.Dir(dpath)
- }
- absDir, err := filepath.Abs(dpath)
- if err != nil {
- return "", err
- }
- goPaths := os.Getenv("GOPATH")
- if goPaths == "" {
- return "", fmt.Errorf("GOPATH not set")
- }
- for _, goPath := range strings.Split(goPaths, ":") {
- srcPath := path.Join(goPath, "src")
- if !strings.HasPrefix(absDir, srcPath) {
- continue
- }
- return strings.Trim(absDir[len(srcPath):], "/"), nil
- }
- return "", fmt.Errorf("give package not under $GOPATH")
- }
- // Parse service spec with gived path and receiver name
- func Parse(name, dpath, recvName, workDir string) (*types.ServiceSpec, error) {
- if workDir == "" {
- workDir, _ = os.Getwd()
- }
- ps := &parseState{
- name: strings.Title(name),
- dpath: dpath,
- recvName: recvName,
- workDir: workDir,
- }
- return ps.parse()
- }
- type parseState struct {
- dpath string
- recvName string
- name string
- workDir string
- typedb map[string]types.Typer
- importPath string
- packageName string
- methods []*types.Method
- }
- func (p *parseState) parse() (spec *types.ServiceSpec, err error) {
- p.typedb = make(map[string]types.Typer)
- if p.importPath, err = GoPackage(p.dpath); err != nil {
- return
- }
- if err := p.searchMethods(); err != nil {
- return nil, err
- }
- return &types.ServiceSpec{
- ImportPath: p.importPath,
- Name: p.name,
- Package: p.packageName,
- Receiver: p.recvName,
- Methods: p.methods,
- }, nil
- }
- func (p *parseState) searchMethods() error {
- fset := token.NewFileSet()
- pkgs, err := parser.ParseDir(fset, p.dpath, nil, parser.ParseComments)
- if err != nil {
- return err
- }
- if len(pkgs) == 0 {
- return fmt.Errorf("no package found on %s", p.dpath)
- }
- if len(pkgs) > 1 {
- return fmt.Errorf("multiple package found on %s", p.dpath)
- }
- for pkgName, pkg := range pkgs {
- //log.Printf("search method in package %s", pkgName)
- p.packageName = pkgName
- for fn, f := range pkg.Files {
- //log.Printf("search method in file %s", fn)
- if err = p.searchMethodsInFile(pkg, f); err != nil {
- log.Printf("search method in %s err %s", fn, err)
- }
- }
- }
- return nil
- }
- func (p *parseState) searchMethodsInFile(pkg *ast.Package, f *ast.File) error {
- for _, decl := range f.Decls {
- funcDecl, ok := decl.(*ast.FuncDecl)
- if !ok || !funcDecl.Name.IsExported() || funcDecl.Recv == nil || len(funcDecl.Recv.List) == 0 {
- continue
- }
- var recvIdent *ast.Ident
- recvField := funcDecl.Recv.List[0]
- switch rt := recvField.Type.(type) {
- case *ast.Ident:
- recvIdent = rt
- case *ast.StarExpr:
- recvIdent = rt.X.(*ast.Ident)
- }
- if recvIdent == nil {
- return fmt.Errorf("unknown recv %v", recvField)
- }
- if recvIdent.Name != p.recvName {
- continue
- }
- log.Printf("find method %s", funcDecl.Name.Name)
- if err := p.parseFuncDecl(pkg, f, funcDecl); err != nil {
- return err
- }
- }
- return nil
- }
- func (p *parseState) parseFuncDecl(pkg *ast.Package, f *ast.File, funcDecl *ast.FuncDecl) error {
- //log.Printf("parse method %s", funcDecl.Name.Name)
- comments, options := parseComments(funcDecl)
- for _, option := range options {
- if option == "ignore" {
- log.Printf("ignore method %s", funcDecl.Name.Name)
- return nil
- }
- }
- ps := typeState{
- File: f,
- ImportPath: p.importPath,
- Pkg: pkg,
- WorkDir: p.workDir,
- typedb: p.typedb,
- PkgDir: p.dpath,
- }
- parameters, err := ps.parseFieldList(funcDecl.Type.Params, false)
- if err != nil {
- return err
- }
- results, err := ps.parseFieldList(funcDecl.Type.Results, false)
- if err != nil {
- return err
- }
- method := &types.Method{
- Name: funcDecl.Name.Name,
- Comments: comments,
- Options: options,
- Parameters: parameters,
- Results: results,
- }
- p.methods = append(p.methods, method)
- return nil
- }
- type typeState struct {
- typedb map[string]types.Typer
- ImportPath string
- Pkg *ast.Package
- File *ast.File
- WorkDir string
- PkgDir string
- }
- func (t *typeState) parseType(expr ast.Expr, ident string) (types.Typer, error) {
- oldFile := t.File
- defer func() {
- t.File = oldFile
- }()
- switch exp := expr.(type) {
- case *ast.Ident:
- if isBuildIn(exp.Name) {
- return &types.BasicType{Name: exp.Name}, nil
- }
- tid := fmt.Sprintf("%s-%s-%s", t.ImportPath, t.Pkg.Name, exp.Name)
- if ty, ok := t.typedb[tid]; ok {
- return ty, nil
- }
- ty, err := t.searchType(exp)
- if err != nil {
- return nil, err
- }
- t.typedb[tid] = ty
- return ty, nil
- case *ast.StarExpr:
- t, err := t.parseType(exp.X, ident)
- if err != nil {
- return nil, err
- }
- return t.SetReference(), nil
- case *ast.SelectorExpr:
- return t.parseSel(exp)
- case *ast.ArrayType:
- et, err := t.parseType(exp.Elt, ident)
- if err != nil {
- return nil, err
- }
- return &types.ArrayType{EltType: et}, nil
- case *ast.MapType:
- kt, err := t.parseType(exp.Key, ident)
- if err != nil {
- return nil, err
- }
- vt, err := t.parseType(exp.Value, ident)
- if err != nil {
- return nil, err
- }
- return &types.MapType{KeyType: kt, ValueType: vt}, nil
- case *ast.InterfaceType:
- return &types.InterfaceType{
- ImportPath: t.ImportPath,
- Package: t.Pkg.Name,
- IdentName: ident,
- }, nil
- case *ast.StructType:
- fields, err := t.parseFieldList(exp.Fields, true)
- return &types.StructType{
- IdentName: ident,
- ImportPath: t.ImportPath,
- Package: t.Pkg.Name,
- Fields: fields,
- ProtoFile: findProtoFile(t.PkgDir, t.File),
- }, err
- }
- return nil, fmt.Errorf("unexpect expr %v", expr)
- }
- func (t *typeState) searchType(ident *ast.Ident) (types.Typer, error) {
- //log.Printf("search type %s", ident.Name)
- for fn, f := range t.Pkg.Files {
- //log.Printf("search in %s", fn)
- for _, decl := range f.Decls {
- if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
- for _, spec := range genDecl.Specs {
- typeSpec, ok := spec.(*ast.TypeSpec)
- if !ok {
- return nil, fmt.Errorf("expect typeSpec get %v in file %s", spec, fn)
- }
- if typeSpec.Name.Name == ident.Name {
- //log.Printf("found in %s", fn)
- t.File = f
- return t.parseType(typeSpec.Type, ident.Name)
- }
- }
- }
- }
- }
- return nil, fmt.Errorf("type %s not found in package %s", ident.Name, t.Pkg.Name)
- }
- func lockType(pkg *ast.Package, ident *ast.Ident) (*ast.File, error) {
- //log.Printf("lock type %s", ident.Name)
- for fn, f := range pkg.Files {
- //log.Printf("search in %s", fn)
- for _, decl := range f.Decls {
- if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
- for _, spec := range genDecl.Specs {
- typeSpec, ok := spec.(*ast.TypeSpec)
- if !ok {
- return nil, fmt.Errorf("expect typeSpec get %v in file %s fn", spec, fn)
- }
- if typeSpec.Name.Name == ident.Name {
- return f, nil
- }
- }
- }
- }
- }
- return nil, fmt.Errorf("type %s not found in package %s", ident.Name, pkg.Name)
- }
- func (t *typeState) parseFieldList(fl *ast.FieldList, filterExported bool) ([]*types.Field, error) {
- fields := make([]*types.Field, 0, fl.NumFields())
- if fl == nil {
- return fields, nil
- }
- for _, af := range fl.List {
- ty, err := t.parseType(af.Type, "")
- if err != nil {
- return nil, err
- }
- if af.Names == nil {
- fields = append(fields, &types.Field{Type: ty})
- } else {
- for _, name := range af.Names {
- if filterExported && !name.IsExported() {
- continue
- }
- fields = append(fields, &types.Field{Type: ty, Name: name.Name})
- }
- }
- }
- return fields, nil
- }
- func (t *typeState) parseSel(sel *ast.SelectorExpr) (types.Typer, error) {
- //log.Printf("parse sel %v.%v", sel.X, sel.Sel)
- x, ok := sel.X.(*ast.Ident)
- if !ok {
- return nil, fmt.Errorf("unsupport sel.X type %v", sel.X)
- }
- var pkg *ast.Package
- var pkgPath string
- var err error
- var importPath string
- var found bool
- var pkgs map[string]*ast.Package
- for _, spec := range t.File.Imports {
- importPath = strings.Trim(spec.Path.Value, "\"")
- if spec.Name != nil && spec.Name.Name == x.Name {
- pkgPath, err = importPackage(t.WorkDir, importPath)
- if err != nil {
- return nil, err
- }
- pkgs, err = parser.ParseDir(token.NewFileSet(), pkgPath, nil, parser.ParseComments)
- if err != nil {
- return nil, err
- }
- pkg, err = filterPkgs(pkgs)
- if err != nil {
- return nil, err
- }
- found = true
- break
- }
- pkgPath, err = importPackage(t.WorkDir, importPath)
- if err != nil {
- return nil, err
- }
- pkgs, err = parser.ParseDir(token.NewFileSet(), pkgPath, nil, parser.ParseComments)
- if err != nil {
- return nil, err
- }
- if pkg, ok = pkgs[x.Name]; ok {
- found = true
- break
- }
- }
- if !found {
- return nil, fmt.Errorf("can't found type %s.%s", x.Name, sel.Sel.Name)
- }
- file, err := lockType(pkg, sel.Sel)
- if err != nil {
- return nil, err
- }
- ts := &typeState{
- File: file,
- Pkg: pkg,
- ImportPath: importPath,
- WorkDir: t.WorkDir,
- typedb: t.typedb,
- PkgDir: pkgPath,
- }
- return ts.searchType(sel.Sel)
- }
- func filterPkgs(pkgs map[string]*ast.Package) (*ast.Package, error) {
- for pname, pkg := range pkgs {
- if strings.HasSuffix(pname, "_test") {
- continue
- }
- return pkg, nil
- }
- return nil, fmt.Errorf("no package found")
- }
- func importPackage(workDir, importPath string) (string, error) {
- //log.Printf("import package %s", importPath)
- searchPaths := make([]string, 0, 3)
- searchPaths = append(searchPaths, path.Join(runtime.GOROOT(), "src"))
- if vendorDir, ok := searchVendor(workDir); ok {
- searchPaths = append(searchPaths, vendorDir)
- }
- for _, goPath := range strings.Split(os.Getenv("GOPATH"), ":") {
- searchPaths = append(searchPaths, path.Join(goPath, "src"))
- }
- var pkgPath string
- var found bool
- for _, basePath := range searchPaths {
- pkgPath = path.Join(basePath, importPath)
- if stat, err := os.Stat(pkgPath); err == nil && stat.IsDir() {
- found = true
- break
- }
- }
- if !found {
- return "", fmt.Errorf("can't import package %s", importPath)
- }
- return pkgPath, nil
- }
- func searchVendor(workDir string) (vendorDir string, ok bool) {
- var err error
- if workDir, err = filepath.Abs(workDir); err != nil {
- return "", false
- }
- goPath := os.Getenv("GOPATH")
- for {
- if !strings.HasPrefix(workDir, goPath) {
- break
- }
- vendorDir := path.Join(workDir, "vendor")
- if stat, err := os.Stat(vendorDir); err == nil && stat.IsDir() {
- return vendorDir, true
- }
- workDir = filepath.Dir(workDir)
- }
- return
- }
- func parseComments(funcDecl *ast.FuncDecl) (comments []string, options []string) {
- if funcDecl.Doc == nil {
- return
- }
- for _, comment := range funcDecl.Doc.List {
- text := strings.TrimLeft(comment.Text, "/ ")
- if strings.HasPrefix(text, optionsPrefix) {
- options = append(options, text[len(optionsPrefix):])
- } else {
- comments = append(comments, text)
- }
- }
- return
- }
- func isBuildIn(t string) bool {
- switch t {
- case "bool", "byte", "complex128", "complex64", "error", "float32",
- "float64", "int", "int16", "int32", "int64", "int8",
- "rune", "string", "uint", "uint16", "uint32", "uint64", "uint8", "uintptr":
- return true
- }
- return false
- }
- func findProtoFile(pkgDir string, f *ast.File) string {
- if f.Comments == nil {
- return ""
- }
- for _, comment := range f.Comments {
- if comment.List == nil {
- continue
- }
- for _, line := range comment.List {
- if protoFile := extractProtoFile(line.Text); protoFile != "" {
- fixPath := path.Join(pkgDir, protoFile)
- if s, err := os.Stat(fixPath); err == nil && !s.IsDir() {
- return fixPath
- }
- return protoFile
- }
- }
- }
- return ""
- }
- func extractProtoFile(line string) string {
- matchs := protoFileRegexp.FindStringSubmatch(line)
- if len(matchs) > 1 {
- return matchs[1]
- }
- return ""
- }
|