mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 03:39:07 +08:00
Support bound variables (struct literals)
Factor out unaryexpression parsing Fix package bleed between tests
This commit is contained in:
parent
4a8917ecbc
commit
ecc1791420
@ -7,9 +7,17 @@ This package contains the static analyser used for parsing Wails projects so tha
|
||||
|
||||
## 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
|
||||
- [ ] Method parameters
|
||||
- [x] Method parameters
|
||||
- [x] Zero parameters
|
||||
- [x] Single input 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] bool
|
||||
- [x] Pointer
|
||||
- [ ] Struct
|
||||
- [x] Struct
|
||||
- [x] Pointer
|
||||
- [x] Recursive
|
||||
- [x] Slices
|
||||
- [x] Pointer
|
||||
- [x] Maps
|
||||
- [x] Pointer
|
||||
- [ ] Model Parsing
|
||||
- [x] Model Parsing
|
||||
- [x] In same package
|
||||
- [ ] In different package
|
||||
- [ ] Multiple named fields, e.g. one,two,three string
|
||||
- [ ] Scalars
|
||||
- [ ] Arrays
|
||||
- [ ] Maps
|
||||
- [ ] Structs
|
||||
- [ ] Anonymous structs
|
||||
- [x] In different package
|
||||
- [x] Multiple named fields, e.g. one,two,three string
|
||||
- [x] Scalars
|
||||
- [x] Arrays
|
||||
- [x] Maps
|
||||
- [x] Structs
|
||||
- [x] Recursive
|
||||
- [x] Anonymous
|
||||
- [ ] Generation of models
|
||||
- [ ] Scalars
|
||||
- [ ] Arrays
|
||||
@ -51,7 +59,7 @@ This package contains the static analyser used for parsing Wails projects so tha
|
||||
|
||||
## 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
|
||||
- Bound structs must be declared as struct literals
|
||||
|
@ -12,8 +12,6 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var packageCache = make(map[string]*ParsedPackage)
|
||||
|
||||
type packagePath = string
|
||||
type structName = string
|
||||
|
||||
@ -59,6 +57,7 @@ type ParsedPackage struct {
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
packageCache map[string]*ParsedPackage
|
||||
Path string
|
||||
BoundMethods map[packagePath]map[structName][]*BoundMethod
|
||||
Models map[packagePath]map[structName]*StructDef
|
||||
@ -68,6 +67,7 @@ type Project struct {
|
||||
func ParseProject(projectPath string) (*Project, error) {
|
||||
result := &Project{
|
||||
BoundMethods: make(map[packagePath]map[structName][]*BoundMethod),
|
||||
packageCache: make(map[string]*ParsedPackage),
|
||||
}
|
||||
pkgs, err := result.parseDirectory(projectPath)
|
||||
if err != nil {
|
||||
@ -78,7 +78,7 @@ func ParseProject(projectPath string) (*Project, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pkg := range packageCache {
|
||||
for _, pkg := range result.packageCache {
|
||||
if len(pkg.StructCache) > 0 {
|
||||
if result.Models == nil {
|
||||
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) {
|
||||
if packageCache[dir] != nil {
|
||||
return map[string]*ParsedPackage{dir: packageCache[dir]}, nil
|
||||
if p.packageCache[dir] != nil {
|
||||
return map[string]*ParsedPackage{dir: p.packageCache[dir]}, nil
|
||||
}
|
||||
// Parse the directory
|
||||
fset := token.NewFileSet()
|
||||
@ -115,7 +115,7 @@ func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error)
|
||||
Dir: getDirectoryForPackage(pkg),
|
||||
StructCache: make(map[structName]*StructDef),
|
||||
}
|
||||
packageCache[packageName] = parsedPackage
|
||||
p.packageCache[packageName] = parsedPackage
|
||||
result[packageName] = parsedPackage
|
||||
}
|
||||
return result, nil
|
||||
@ -201,66 +201,44 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
|
||||
// Check the element is a unary expression
|
||||
unaryExpr, ok := elt.(*ast.UnaryExpr)
|
||||
if ok {
|
||||
// Check the unary expression is a composite lit
|
||||
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
|
||||
if !ok {
|
||||
result, shouldContinue := p.parseBoundUnaryExpression(unaryExpr, pkg)
|
||||
if shouldContinue {
|
||||
continue
|
||||
}
|
||||
// Check if the composite lit is a struct
|
||||
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
|
||||
// Parse struct
|
||||
return result
|
||||
}
|
||||
// Check the element is a variable
|
||||
ident, ok := elt.(*ast.Ident)
|
||||
if ok {
|
||||
println("found: ", ident.Name)
|
||||
if ident.Obj == nil {
|
||||
continue
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// Check if the variable is a struct
|
||||
if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
|
||||
continue
|
||||
// Check if the ident is a struct type
|
||||
//if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||
// var parsedStruct *StructDefinition
|
||||
// parsedStruct, err = p.parseStruct(typeSpec, thisPackage)
|
||||
// if err != nil {
|
||||
// return true
|
||||
// }
|
||||
// p.addModel(thisPackage.Name, parsedStruct)
|
||||
// p.addBoundStruct(thisPackage.Name, ident.Name)
|
||||
// continue
|
||||
//}
|
||||
//// Check if the ident is a struct type
|
||||
//if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||
// thisPackage.boundStructs[ident.Name] = &BoundStruct{
|
||||
// Name: ident.Name,
|
||||
// }
|
||||
// continue
|
||||
//}
|
||||
// Check the typespec decl is a struct
|
||||
//if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
|
||||
// continue
|
||||
//}
|
||||
|
||||
}
|
||||
// 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
|
||||
}
|
||||
// Check if the variable is a type
|
||||
if typeSpec, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
||||
// Check if the type is a struct
|
||||
if _, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Check if it's an assignment
|
||||
if assign, ok := ident.Obj.Decl.(*ast.AssignStmt); ok {
|
||||
// Check if the assignment is a struct
|
||||
if _, ok := assign.Rhs[0].(*ast.StructType); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := assign.Rhs[0].(*ast.UnaryExpr); ok {
|
||||
result, shouldContinue := p.parseBoundUnaryExpression(assign.Rhs[0].(*ast.UnaryExpr), pkg)
|
||||
if shouldContinue {
|
||||
continue
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +254,47 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
|
||||
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) {
|
||||
_, ok := p.BoundMethods[packagePath]
|
||||
if !ok {
|
||||
@ -488,7 +507,7 @@ func (p *Project) getParsedPackageFromName(packageName string, currentPackage *P
|
||||
Dir: dir,
|
||||
StructCache: make(map[string]*StructDef),
|
||||
}
|
||||
packageCache[path] = result
|
||||
p.packageCache[path] = result
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
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 {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -160,6 +160,10 @@ func (*GreetService) StructPointerInputErrorOutput(in *Person) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*GreetService) StructInputStructOutput(in Person) Person {
|
||||
return in
|
||||
}
|
||||
|
||||
func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person {
|
||||
return in
|
||||
}
|
||||
|
36
v3/internal/parser/testdata/variable_single/main.go
vendored
Normal file
36
v3/internal/parser/testdata/variable_single/main.go
vendored
Normal 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)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user