From cd11c0a83ca907c7b58719070dbc130a13776cbe Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Fri, 24 Feb 2023 21:15:39 +1100 Subject: [PATCH] Support references to structs in other packages --- v3/internal/parser/parser.go | 51 ++++++++--- .../{parser_types_test.go => parser_test.go} | 87 ++++++++++++++++++- .../struct_literal_multiple_other/main.go | 3 +- .../services/other.go | 16 +++- 4 files changed, 138 insertions(+), 19 deletions(-) rename v3/internal/parser/{parser_types_test.go => parser_test.go} (91%) diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index 821908fdc..69e01862f 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -6,6 +6,7 @@ import ( "go/build" "go/parser" "go/token" + "log" "os" "path/filepath" "strconv" @@ -29,6 +30,7 @@ type ParameterType struct { IsPointer bool MapKey *ParameterType MapValue *ParameterType + Package string } type Parameter struct { @@ -59,6 +61,7 @@ type ParsedPackage struct { type Project struct { Path string BoundMethods map[packagePath]map[structName][]*BoundMethod + Models map[packagePath]map[structName]*StructDef } func ParseProject(projectPath string) (*Project, error) { @@ -74,13 +77,19 @@ func ParseProject(projectPath string) (*Project, error) { if err != nil { return nil, err } + for _, pkg := range packageCache { + if len(pkg.StructCache) > 0 { + if result.Models == nil { + result.Models = make(map[packagePath]map[structName]*StructDef) + } + result.Models[pkg.Path] = pkg.StructCache + } + } return result, nil } func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { - println("Parsing directory " + dir) if packageCache[dir] != nil { - println("Found directory in cache!") return map[string]*ParsedPackage{dir: packageCache[dir]}, nil } // Parse the directory @@ -105,7 +114,7 @@ func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) Dir: getDirectoryForPackage(pkg), StructCache: make(map[structName]*StructDef), } - packageCache[dir] = parsedPackage + packageCache[packageName] = parsedPackage result[packageName] = parsedPackage } return result, nil @@ -115,9 +124,8 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e var callFound bool - for packageName, pkg := range pkgs { + for _, pkg := range pkgs { thisPackage := pkg.Pkg - println(" - Looking in package: " + packageName) // Iterate through the package's files for _, file := range thisPackage.Files { // Use an ast.Inspector to find the calls to application.New @@ -338,12 +346,20 @@ func (p *Project) parseParameterType(field *ast.Field, pkg *ParsedPackage) *Para result := &ParameterType{} result.Name = getTypeString(field.Type) switch t := field.Type.(type) { + case *ast.Ident: + result.IsStruct = isStructType(t) case *ast.StarExpr: result = p.parseParameterType(&ast.Field{Type: t.X}, pkg) result.IsPointer = true - result.IsStruct = isStructType(t.X) case *ast.StructType: result.IsStruct = true + case *ast.SelectorExpr: + extPackage, err := p.getParsedPackageFromName(t.X.(*ast.Ident).Name, pkg) + if err != nil { + log.Fatal(err) + } + result.IsStruct = p.getStructDef(t.Sel.Name, extPackage) + result.Package = extPackage.Path case *ast.ArrayType: result.IsSlice = true result.IsStruct = isStructType(t.Elt) @@ -355,18 +371,18 @@ func (p *Project) parseParameterType(field *ast.Field, pkg *ParsedPackage) *Para default: } if result.IsStruct { - _, ok := pkg.StructCache[result.Name] - if !ok { - p.getStructDef(result.Name, pkg) + p.getStructDef(result.Name, pkg) + if result.Package == "" { + result.Package = pkg.Path } } return result } -func (p *Project) getStructDef(name string, pkg *ParsedPackage) { +func (p *Project) getStructDef(name string, pkg *ParsedPackage) bool { _, ok := pkg.StructCache[name] if ok { - return + return true } // Iterate over all files in the package for _, file := range pkg.Pkg.Files { @@ -386,6 +402,7 @@ func (p *Project) getStructDef(name string, pkg *ParsedPackage) { } pkg.StructCache[name] = result result.Fields = p.parseStructFields(structType, pkg) + return true } } } @@ -394,6 +411,7 @@ func (p *Project) getStructDef(name string, pkg *ParsedPackage) { } } } + return false } func (p *Project) parseStructFields(structType *ast.StructType, pkg *ParsedPackage) []*Field { @@ -419,6 +437,9 @@ func (p *Project) parseStructFields(structType *ast.StructType, pkg *ParsedPacka if !ok { p.getStructDef(paramType.Name, pkg) } + if paramType.Package == "" { + paramType.Package = pkg.Path + } } thisField.Type = paramType result = append(result, thisField) @@ -445,13 +466,15 @@ func (p *Project) getParsedPackageFromName(packageName string, currentPackage *P if err != nil { return nil, err } - return &ParsedPackage{ + result := &ParsedPackage{ Pkg: pkg, Name: packageName, Path: path, Dir: dir, StructCache: make(map[string]*StructDef), - }, nil + } + packageCache[path] = result + return result, nil } } } @@ -490,6 +513,8 @@ func getTypeString(expr ast.Expr) string { return getTypeString(t.Elt) case *ast.MapType: return "map" + case *ast.SelectorExpr: + return getTypeString(t.Sel) default: return "any" } diff --git a/v3/internal/parser/parser_types_test.go b/v3/internal/parser/parser_test.go similarity index 91% rename from v3/internal/parser/parser_types_test.go rename to v3/internal/parser/parser_test.go index e5563be76..f14b8ff20 100644 --- a/v3/internal/parser/parser_types_test.go +++ b/v3/internal/parser/parser_test.go @@ -11,6 +11,7 @@ func TestParseDirectory(t *testing.T) { name string dir string wantBoundMethods map[string]map[string][]*BoundMethod + wantModels map[string]map[string]*StructDef wantErr bool }{ { @@ -689,6 +690,7 @@ func TestParseDirectory(t *testing.T) { Name: "Person", IsPointer: true, IsStruct: true, + Package: "main", }, }, }, @@ -709,6 +711,7 @@ func TestParseDirectory(t *testing.T) { Name: "Person", IsPointer: true, IsStruct: true, + Package: "main", }, }, }, @@ -718,6 +721,7 @@ func TestParseDirectory(t *testing.T) { Name: "Person", IsPointer: true, IsStruct: true, + Package: "main", }, }, }, @@ -841,6 +845,30 @@ func TestParseDirectory(t *testing.T) { }, }, }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Parent", + Type: &ParameterType{ + Name: "Person", + IsStruct: true, + IsPointer: true, + Package: "main", + }, + }, + }, + }, + }, + }, wantErr: false, }, { @@ -960,6 +988,7 @@ func TestParseDirectory(t *testing.T) { Name: "Person", IsPointer: true, IsStruct: true, + Package: "main", }, }, }, @@ -973,8 +1002,10 @@ func TestParseDirectory(t *testing.T) { Outputs: []*Parameter{ { Type: &ParameterType{ - Name: "int", - IsSlice: true, + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", }, }, }, @@ -982,6 +1013,55 @@ func TestParseDirectory(t *testing.T) { }, }, }, + wantModels: map[string]map[string]*StructDef{ + "main": { + "Person": { + Name: "Person", + Fields: []*Field{ + { + Name: "Name", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Address", + Type: &ParameterType{ + Name: "Address", + IsStruct: true, + IsPointer: true, + Package: "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services", + }, + }, + }, + }, + }, + "github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services": { + "Address": { + Name: "Address", + Fields: []*Field{ + { + Name: "Street", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "State", + Type: &ParameterType{ + Name: "string", + }, + }, + { + Name: "Country", + Type: &ParameterType{ + Name: "string", + }, + }, + }, + }, + }, + }, }, } for _, tt := range tests { @@ -994,6 +1074,9 @@ func TestParseDirectory(t *testing.T) { if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { t.Errorf("ParseDirectory() failed:\n" + diff) } + if diff := cmp.Diff(tt.wantModels, got.Models); diff != "" { + t.Errorf("ParseDirectory() failed:\n" + diff) + } }) } diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go index cf020662f..8c6953dbb 100644 --- a/v3/internal/parser/testdata/struct_literal_multiple_other/main.go +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/main.go @@ -16,7 +16,8 @@ type GreetService struct { } type Person struct { - Name string + Name string + Address *services.Address } // Greet does XYZ diff --git a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go index 76c0c5b00..55472595b 100644 --- a/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go +++ b/v3/internal/parser/testdata/struct_literal_multiple_other/services/other.go @@ -6,7 +6,17 @@ type OtherService struct { t int } -// Yay does this and that -func (o *OtherService) Yay() []int { - return []int{0, 1, 2} +type Address struct { + Street string + State string + Country string +} + +// Yay does this and that +func (o *OtherService) Yay() *Address { + return &Address{ + Street: "123 Pitt Street", + State: "New South Wales", + Country: "Australia", + } }