5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 18:29:53 +08:00

Support bound variables (struct literals)

Factor out unaryexpression parsing
Fix package bleed between tests
This commit is contained in:
Lea Anthony 2023-02-25 09:47:50 +11:00
parent 4a8917ecbc
commit ecc1791420
5 changed files with 192 additions and 72 deletions

View File

@ -7,9 +7,17 @@ This package contains the static analyser used for parsing Wails projects so tha
## Implemented ## Implemented
- [ ] Parsing of bound methods - [ ] Bound types
- [x] Struct Literal Pointer
- [ ] Variable
- [ ] Assignment
- [x] Struct Literal Pointer
- [ ] Function
- [ ] Function
- [x] Parsing of bound methods
- [x] Method names - [x] Method names
- [ ] Method parameters - [x] Method parameters
- [x] Zero parameters - [x] Zero parameters
- [x] Single input parameter - [x] Single input parameter
- [x] Single output parameter - [x] Single output parameter
@ -26,22 +34,22 @@ This package contains the static analyser used for parsing Wails projects so tha
- [x] Pointer - [x] Pointer
- [x] bool - [x] bool
- [x] Pointer - [x] Pointer
- [ ] Struct - [x] Struct
- [x] Pointer - [x] Pointer
- [x] Recursive
- [x] Slices - [x] Slices
- [x] Pointer - [x] Pointer
- [x] Maps - [x] Maps
- [x] Pointer - [x] Pointer
- [ ] Model Parsing - [x] Model Parsing
- [x] In same package - [x] In same package
- [ ] In different package - [x] In different package
- [ ] Multiple named fields, e.g. one,two,three string - [x] Multiple named fields, e.g. one,two,three string
- [ ] Scalars - [x] Scalars
- [ ] Arrays - [x] Arrays
- [ ] Maps - [x] Maps
- [ ] Structs - [x] Structs
- [ ] Anonymous structs - [x] Recursive
- [x] Anonymous
- [ ] Generation of models - [ ] Generation of models
- [ ] Scalars - [ ] Scalars
- [ ] Arrays - [ ] Arrays
@ -51,7 +59,7 @@ This package contains the static analyser used for parsing Wails projects so tha
## Limitations ## Limitations
There are many ways to write a Go program so there are many different program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are: There are many ways to write a Go program so there are many program structures that we would need to support. This is a work in progress and will be improved over time. The current limitations are:
- The call to `application.New()` must be in the `main` package - The call to `application.New()` must be in the `main` package
- Bound structs must be declared as struct literals - Bound structs must be declared as struct literals

View File

@ -12,8 +12,6 @@ import (
"strconv" "strconv"
) )
var packageCache = make(map[string]*ParsedPackage)
type packagePath = string type packagePath = string
type structName = string type structName = string
@ -59,6 +57,7 @@ type ParsedPackage struct {
} }
type Project struct { type Project struct {
packageCache map[string]*ParsedPackage
Path string Path string
BoundMethods map[packagePath]map[structName][]*BoundMethod BoundMethods map[packagePath]map[structName][]*BoundMethod
Models map[packagePath]map[structName]*StructDef Models map[packagePath]map[structName]*StructDef
@ -68,6 +67,7 @@ type Project struct {
func ParseProject(projectPath string) (*Project, error) { func ParseProject(projectPath string) (*Project, error) {
result := &Project{ result := &Project{
BoundMethods: make(map[packagePath]map[structName][]*BoundMethod), BoundMethods: make(map[packagePath]map[structName][]*BoundMethod),
packageCache: make(map[string]*ParsedPackage),
} }
pkgs, err := result.parseDirectory(projectPath) pkgs, err := result.parseDirectory(projectPath)
if err != nil { if err != nil {
@ -78,7 +78,7 @@ func ParseProject(projectPath string) (*Project, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, pkg := range packageCache { for _, pkg := range result.packageCache {
if len(pkg.StructCache) > 0 { if len(pkg.StructCache) > 0 {
if result.Models == nil { if result.Models == nil {
result.Models = make(map[packagePath]map[structName]*StructDef) result.Models = make(map[packagePath]map[structName]*StructDef)
@ -90,8 +90,8 @@ func ParseProject(projectPath string) (*Project, error) {
} }
func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) { func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) {
if packageCache[dir] != nil { if p.packageCache[dir] != nil {
return map[string]*ParsedPackage{dir: packageCache[dir]}, nil return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil
} }
// Parse the directory // Parse the directory
fset := token.NewFileSet() fset := token.NewFileSet()
@ -115,7 +115,7 @@ func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error)
Dir: getDirectoryForPackage(pkg), Dir: getDirectoryForPackage(pkg),
StructCache: make(map[structName]*StructDef), StructCache: make(map[structName]*StructDef),
} }
packageCache[packageName] = parsedPackage p.packageCache[packageName] = parsedPackage
result[packageName] = parsedPackage result[packageName] = parsedPackage
} }
return result, nil return result, nil
@ -201,67 +201,45 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
// Check the element is a unary expression // Check the element is a unary expression
unaryExpr, ok := elt.(*ast.UnaryExpr) unaryExpr, ok := elt.(*ast.UnaryExpr)
if ok { if ok {
// Check the unary expression is a composite lit result, shouldContinue := p.parseBoundUnaryExpression(unaryExpr, pkg)
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit) if shouldContinue {
if !ok {
continue continue
} }
// Check if the composite lit is a struct return result
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
// Parse struct
continue
} }
// Check if the lit is an ident // Check the element is a variable
ident, ok := boundStructLit.Type.(*ast.Ident) ident, ok := elt.(*ast.Ident)
if ok { if ok {
err = p.parseBoundStructMethods(ident.Name, pkg) println("found: ", ident.Name)
if err != nil { if ident.Obj == nil {
return true
}
continue continue
// Check if the ident is a struct type }
//if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { // Check if the variable is a struct
// var parsedStruct *StructDefinition if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
// parsedStruct, err = p.parseStruct(typeSpec, thisPackage) continue
// if err != nil { }
// return true // Check if the variable is a type
// } if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
// p.addModel(thisPackage.Name, parsedStruct) // Check if the type is a struct
// p.addBoundStruct(thisPackage.Name, ident.Name) if _, ok := typeSpec.Type.(*ast.StructType); ok {
// continue continue
//} }
//// Check if the ident is a struct type }
//if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { // Check if it's an assignment
// thisPackage.boundStructs[ident.Name] = &BoundStruct{ if assign, ok := ident.Obj.Decl.(*ast.AssignStmt); ok {
// Name: ident.Name, // Check if the assignment is a struct
// } if _, ok := assign.Rhs[0].(*ast.StructType); ok {
// continue continue
//} }
// Check the typespec decl is a struct if _, ok := assign.Rhs[0].(*ast.UnaryExpr); ok {
//if _, ok := ident.Obj.Decl.(*ast.StructType); ok { result, shouldContinue := p.parseBoundUnaryExpression(assign.Rhs[0].(*ast.UnaryExpr), pkg)
// continue if shouldContinue {
//} continue
}
return result
}
} }
// Check if the lit is a selector
selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
if ok {
// Check if the selector is an ident
if _, ok := selector.X.(*ast.Ident); ok {
// 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
}
} }
} }
} }
@ -276,6 +254,47 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
return nil return nil
} }
func (p *Project) parseBoundUnaryExpression(unaryExpr *ast.UnaryExpr, pkg *ParsedPackage) (bool, bool) {
// Check the unary expression is a composite lit
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
if !ok {
return false, true
}
// Check if the composite lit is a struct
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
// Parse struct
return false, true
}
// Check if the lit is an ident
ident, ok := boundStructLit.Type.(*ast.Ident)
if ok {
err := p.parseBoundStructMethods(ident.Name, pkg)
if err != nil {
return true, false
}
}
// Check if the lit is a selector
selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
if ok {
// Check if the selector is an ident
if _, ok := selector.X.(*ast.Ident); ok {
// Look up the package
var parsedPackage *ParsedPackage
parsedPackage, err := p.getParsedPackageFromName(selector.X.(*ast.Ident).Name, pkg)
if err != nil {
return true, false
}
err = p.parseBoundStructMethods(selector.Sel.Name, parsedPackage)
if err != nil {
return true, false
}
return false, true
}
return false, true
}
return false, true
}
func (p *Project) addBoundMethods(packagePath string, name string, boundMethods []*BoundMethod) { func (p *Project) addBoundMethods(packagePath string, name string, boundMethods []*BoundMethod) {
_, ok := p.BoundMethods[packagePath] _, ok := p.BoundMethods[packagePath]
if !ok { if !ok {
@ -488,7 +507,7 @@ func (p *Project) getParsedPackageFromName(packageName string, currentPackage *P
Dir: dir, Dir: dir,
StructCache: make(map[string]*StructDef), StructCache: make(map[string]*StructDef),
} }
packageCache[path] = result p.packageCache[path] = result
return result, nil return result, nil
} }
} }

View File

@ -702,6 +702,28 @@ func TestParseDirectory(t *testing.T) {
}, },
}, },
}, },
{
Name: "StructInputStructOutput",
Inputs: []*Parameter{
{
Name: "in",
Type: &ParameterType{
Name: "Person",
IsStruct: true,
Package: "main",
},
},
},
Outputs: []*Parameter{
{
Type: &ParameterType{
Name: "Person",
IsStruct: true,
Package: "main",
},
},
},
},
{ {
Name: "StructPointerInputStructPointerOutput", Name: "StructPointerInputStructPointerOutput",
Inputs: []*Parameter{ Inputs: []*Parameter{
@ -1101,6 +1123,37 @@ func TestParseDirectory(t *testing.T) {
}, },
}, },
}, },
{
name: "should find a bound services using a variable",
dir: "testdata/variable_single",
wantErr: false,
wantBoundMethods: map[string]map[string][]*BoundMethod{
"main": {
"GreetService": {
{
Name: "Greet",
DocComment: "Greet someone\n",
Inputs: []*Parameter{
{
Name: "name",
Type: &ParameterType{
Name: "string",
},
},
},
Outputs: []*Parameter{
{
Name: "",
Type: &ParameterType{
Name: "string",
},
},
},
},
},
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -160,6 +160,10 @@ func (*GreetService) StructPointerInputErrorOutput(in *Person) error {
return nil return nil
} }
func (*GreetService) StructInputStructOutput(in Person) Person {
return in
}
func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person {
return in return in
} }

View File

@ -0,0 +1,36 @@
package main
import (
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
"log"
)
// GreetService is great
type GreetService struct {
SomeVariable int
lowerCase string
}
// Greet someone
func (*GreetService) Greet(name string) string {
return "Hello " + name
}
func main() {
greetService := &GreetService{}
app := application.New(application.Options{
Bind: []interface{}{
greetService,
},
})
app.NewWebviewWindow()
err := app.Run()
if err != nil {
log.Fatal(err)
}
}