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:
parent
4a8917ecbc
commit
ecc1791420
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
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