From 4e5be364592a445af19e3f334b64d7e7b7ba4f89 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 22 Feb 2023 20:36:30 +1100 Subject: [PATCH] Parse structs bound from other packages --- v3/internal/parser/parser.go | 82 +++++++++++++++---- .../parser/parser_external_packages_test.go | 28 ------- v3/internal/parser/parser_types_test.go | 74 +++++++++++++++-- 3 files changed, 132 insertions(+), 52 deletions(-) delete mode 100644 v3/internal/parser/parser_external_packages_test.go diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index 5c8a4c61..15ce1333 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -3,10 +3,12 @@ package parser import ( "fmt" "go/ast" + "go/build" "go/parser" "go/token" "os" "path/filepath" + "strconv" ) var packageCache = make(map[string]*ParsedPackage) @@ -235,24 +237,16 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e if ok { // Check if the selector is an ident if _, ok := selector.X.(*ast.Ident); ok { - //// Check if the ident is a package - //if _, ok := context.packages[ident.Name]; !ok { - // externalPackage, err := context.getPackageFromPath(ident.Name) - // if err != nil { - // println("Error getting package from path: " + err.Error()) - // return true - // } - // context.packages[ident.Name] = &parsedPackage{ - // name: ident.Name, - // pkg: externalPackage, - // boundStructs: make(map[string]*BoundStruct), - // } - //} - //context.packages[ident.Name].boundStructs[selector.Sel.Name] = &BoundStruct{ - // Name: selector.Sel.Name, - //} - //p.parseStructFromExternalPackage(selector.Sel.Name, ident.Name, thisPackage) - //p.addBoundStruct(ident.Name, selector.Sel.Name) + // Look up the package + var parsedPackage *ParsedPackage + parsedPackage, err = p.getParsedPackageFromName(selector.X.(*ast.Ident).Name, pkg) + if err != nil { + return true + } + err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage) + if err != nil { + return true + } continue } continue @@ -431,6 +425,58 @@ func (p *Project) parseStructFields(structType *ast.StructType, pkg *ParsedPacka return result } +func (p *Project) getParsedPackageFromName(packageName string, currentPackage *ParsedPackage) (*ParsedPackage, error) { + for _, file := range currentPackage.Pkg.Files { + for _, imp := range file.Imports { + path, err := strconv.Unquote(imp.Path.Value) + if err != nil { + return nil, err + } + _, lastPathElement := filepath.Split(path) + if imp.Name != nil && imp.Name.Name == packageName || lastPathElement == packageName { + // Get the directory for the package + dir, err := getPackageDir(path) + if err != nil { + return nil, err + } + pkg, err := p.getPackageFromPath(dir, path) + if err != nil { + return nil, err + } + return &ParsedPackage{ + Pkg: pkg, + Name: packageName, + Dir: dir, + structCache: make(map[string]*StructDef), + }, nil + } + } + } + return nil, fmt.Errorf("package %s not found in %s", packageName, currentPackage.Name) +} + +func getPackageDir(importPath string) (string, error) { + pkg, err := build.Import(importPath, "", build.FindOnly) + if err != nil { + return "", err + } + return pkg.Dir, nil +} + +func (p *Project) getPackageFromPath(packagedir string, packagepath string) (*ast.Package, error) { + impPkg, err := parser.ParseDir(token.NewFileSet(), packagedir, nil, parser.AllErrors) + if err != nil { + return nil, err + } + for impName, impPkg := range impPkg { + if impName == "main" { + continue + } + return impPkg, nil + } + return nil, fmt.Errorf("package not found in imported package %s", packagepath) +} + func getTypeString(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: diff --git a/v3/internal/parser/parser_external_packages_test.go b/v3/internal/parser/parser_external_packages_test.go deleted file mode 100644 index 9e097014..00000000 --- a/v3/internal/parser/parser_external_packages_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package parser - -import ( - "testing" - - "github.com/google/go-cmp/cmp" -) - -func TestExternalPackages(t *testing.T) { - tests := []struct { - name string - dir string - wantBoundMethods map[string]map[string][]*BoundMethod - wantErr bool - }{} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseProject(tt.dir) - if (err != nil) != tt.wantErr { - t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) - return - } - if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { - t.Errorf("ParseDirectory() failed:\n" + diff) - } - }) - } -} diff --git a/v3/internal/parser/parser_types_test.go b/v3/internal/parser/parser_types_test.go index 3c4c144c..524a2ac2 100644 --- a/v3/internal/parser/parser_types_test.go +++ b/v3/internal/parser/parser_types_test.go @@ -915,12 +915,74 @@ func TestParseDirectory(t *testing.T) { }, wantErr: false, }, - //{ - // name: "should find multiple bound services over multiple packages", - // dir: "testdata/struct_literal_multiple_other", - // //wantModels: []string{"main.GreetService", "services.OtherService", "main.Person"}, - // wantErr: false, - //}, + { + name: "should find multiple bound services over multiple packages", + dir: "testdata/struct_literal_multiple_other", + wantErr: false, + wantBoundMethods: map[string]map[string][]*BoundMethod{ + "main": { + "GreetService": { + { + Name: "Greet", + DocComment: "Greet does XYZ\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + { + Name: "NewPerson", + DocComment: "NewPerson creates a new person\n", + Inputs: []*Parameter{ + { + Name: "name", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + }, + }, + }, + }, + }, + }, + "services": { + "OtherService": { + { + Name: "Yay", + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {