mirror of
https://github.com/wavetermdev/wails.git
synced 2026-08-05 13:53:43 -07:00
[v3] Improved parser for bound structs
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/examples/binding/services"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,95 +1,96 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func GenerateModels(context *Context) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
var pkgs []Package
|
||||
specs := context.GetBoundStructs()
|
||||
for pkg, pkgSpecs := range specs {
|
||||
pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs})
|
||||
}
|
||||
knownStructs := newAllModels(specs)
|
||||
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name })
|
||||
for _, pkg := range pkgs {
|
||||
if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
|
||||
for _, spec := range pkg.Specs {
|
||||
if structType, ok := spec.Type.(*ast.StructType); ok {
|
||||
if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
|
||||
// Ignore field names that have a lower case first letter
|
||||
if !unicode.IsUpper(rune(field.Names[0].Name[0])) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the Go type of the field
|
||||
goType := types.ExprString(field.Type)
|
||||
// Check if the type is an array
|
||||
if arrayType, ok := field.Type.(*ast.ArrayType); ok {
|
||||
// Get the element type of the array
|
||||
elementType := types.ExprString(arrayType.Elt)
|
||||
// Look up the corresponding TypeScript type
|
||||
tsType, ok := goToTS[elementType]
|
||||
if !ok {
|
||||
// strip off the * prefix if it is there
|
||||
if strings.HasPrefix(elementType, "*") {
|
||||
elementType = elementType[1:]
|
||||
}
|
||||
if knownStructs.exists(elementType) {
|
||||
tsType = elementType
|
||||
} else {
|
||||
tsType = "any"
|
||||
}
|
||||
}
|
||||
// Output the field as an array of the corresponding TypeScript type
|
||||
if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// strip off the * prefix if it is there
|
||||
if strings.HasPrefix(goType, "*") {
|
||||
goType = goType[1:]
|
||||
}
|
||||
// Look up the corresponding TypeScript type
|
||||
tsType, ok := goToTS[goType]
|
||||
if !ok {
|
||||
if knownStructs.exists(goType) {
|
||||
tsType = goType
|
||||
} else {
|
||||
tsType = "any"
|
||||
}
|
||||
}
|
||||
// Output the field as the corresponding TypeScript type
|
||||
if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
//
|
||||
//import (
|
||||
// "bytes"
|
||||
// "fmt"
|
||||
// "go/ast"
|
||||
// "go/types"
|
||||
// "sort"
|
||||
// "strings"
|
||||
// "unicode"
|
||||
//)
|
||||
//
|
||||
//func GenerateModels(context *Context) ([]byte, error) {
|
||||
// var buf bytes.Buffer
|
||||
// var pkgs []Package
|
||||
// specs := context.GetBoundStructs()
|
||||
// for pkg, pkgSpecs := range specs {
|
||||
// pkgs = append(pkgs, Package{Name: pkg, Specs: pkgSpecs})
|
||||
// }
|
||||
// knownStructs := newAllModels(specs)
|
||||
// sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Name < pkgs[j].Name })
|
||||
// for _, pkg := range pkgs {
|
||||
// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
|
||||
// for _, spec := range pkg.Specs {
|
||||
// if structType, ok := spec.Type.(*ast.StructType); ok {
|
||||
// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// for _, field := range structType.Fields.List {
|
||||
//
|
||||
// // Ignore field names that have a lower case first letter
|
||||
// if !unicode.IsUpper(rune(field.Names[0].Name[0])) {
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// // Get the Go type of the field
|
||||
// goType := types.ExprString(field.Type)
|
||||
// // Check if the type is an array
|
||||
// if arrayType, ok := field.Type.(*ast.ArrayType); ok {
|
||||
// // Get the element type of the array
|
||||
// elementType := types.ExprString(arrayType.Elt)
|
||||
// // Look up the corresponding TypeScript type
|
||||
// tsType, ok := goToTS[elementType]
|
||||
// if !ok {
|
||||
// // strip off the * prefix if it is there
|
||||
// if strings.HasPrefix(elementType, "*") {
|
||||
// elementType = elementType[1:]
|
||||
// }
|
||||
// if knownStructs.exists(elementType) {
|
||||
// tsType = elementType
|
||||
// } else {
|
||||
// tsType = "any"
|
||||
// }
|
||||
// }
|
||||
// // Output the field as an array of the corresponding TypeScript type
|
||||
// if _, err := fmt.Fprintf(&buf, " %s: %s[];\n", field.Names[0].Name, tsType); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// } else {
|
||||
// // strip off the * prefix if it is there
|
||||
// if strings.HasPrefix(goType, "*") {
|
||||
// goType = goType[1:]
|
||||
// }
|
||||
// // Look up the corresponding TypeScript type
|
||||
// tsType, ok := goToTS[goType]
|
||||
// if !ok {
|
||||
// if knownStructs.exists(goType) {
|
||||
// tsType = goType
|
||||
// } else {
|
||||
// tsType = "any"
|
||||
// }
|
||||
// }
|
||||
// // Output the field as the corresponding TypeScript type
|
||||
// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if _, err := fmt.Fprintf(&buf, "}\n\n"); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
// return buf.Bytes(), nil
|
||||
//}
|
||||
|
||||
+256
-329
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,9 +34,9 @@ func TestParseDirectory(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should find bound services from other packages",
|
||||
dir: "../../examples/binding",
|
||||
want: []string{"main.localStruct", "services.GreetService", "models.Person"},
|
||||
name: "should find multiple bound services over multiple packages",
|
||||
dir: "testdata/struct_literal_multiple_other",
|
||||
want: []string{"main.GreetService", "services.OtherService"},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
@@ -50,7 +50,8 @@ func TestParseDirectory(t *testing.T) {
|
||||
}
|
||||
|
||||
for name, pkg := range got.packages {
|
||||
for structName := range pkg.boundStructs {
|
||||
for structName, structType := range pkg.boundStructs {
|
||||
require.NotNil(t, structType)
|
||||
require.True(t, lo.Contains(tt.want, name+"."+structName))
|
||||
tt.want = lo.Without(tt.want, name+"."+structName)
|
||||
}
|
||||
@@ -58,87 +59,88 @@ func TestParseDirectory(t *testing.T) {
|
||||
require.Empty(t, tt.want)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGenerateTypeScript(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "should find single bound service",
|
||||
dir: "testdata/struct_literal_single",
|
||||
want: `namespace main {
|
||||
class GreetService {
|
||||
SomeVariable: number;
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should find multiple bound services",
|
||||
dir: "testdata/struct_literal_multiple",
|
||||
want: `namespace main {
|
||||
class GreetService {
|
||||
SomeVariable: number;
|
||||
}
|
||||
class OtherService {
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should find multiple bound services over multiple files",
|
||||
dir: "testdata/struct_literal_multiple_files",
|
||||
want: `namespace main {
|
||||
class GreetService {
|
||||
SomeVariable: number;
|
||||
}
|
||||
class OtherService {
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "should find bound services from other packages",
|
||||
dir: "../../examples/binding",
|
||||
want: `namespace main {
|
||||
class localStruct {
|
||||
}
|
||||
}
|
||||
namespace models {
|
||||
class Person {
|
||||
Name: string;
|
||||
}
|
||||
}
|
||||
namespace services {
|
||||
class GreetService {
|
||||
SomeVariable: number;
|
||||
Parent: models.Person;
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
Debug = true
|
||||
context, err := ParseDirectory(tt.dir)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
ts, err := GenerateModels(context)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, string(ts))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
//func TestGenerateTypeScript(t *testing.T) {
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// dir string
|
||||
// want string
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "should find single bound service",
|
||||
// dir: "testdata/struct_literal_single",
|
||||
// want: `namespace main {
|
||||
// class GreetService {
|
||||
// SomeVariable: number;
|
||||
// }
|
||||
//}
|
||||
//`,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "should find multiple bound services",
|
||||
// dir: "testdata/struct_literal_multiple",
|
||||
// want: `namespace main {
|
||||
// class GreetService {
|
||||
// SomeVariable: number;
|
||||
// }
|
||||
// class OtherService {
|
||||
// }
|
||||
//}
|
||||
//`,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "should find multiple bound services over multiple files",
|
||||
// dir: "testdata/struct_literal_multiple_files",
|
||||
// want: `namespace main {
|
||||
// class GreetService {
|
||||
// SomeVariable: number;
|
||||
// }
|
||||
// class OtherService {
|
||||
// }
|
||||
//}
|
||||
//`,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "should find bound services from other packages",
|
||||
// dir: "../../examples/binding",
|
||||
// want: `namespace main {
|
||||
// class localStruct {
|
||||
// }
|
||||
//}
|
||||
//namespace models {
|
||||
// class Person {
|
||||
// Name: string;
|
||||
// }
|
||||
//}
|
||||
//namespace services {
|
||||
// class GreetService {
|
||||
// SomeVariable: number;
|
||||
// Parent: models.Person;
|
||||
// }
|
||||
//}
|
||||
//`,
|
||||
// wantErr: false,
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// Debug = true
|
||||
// context, err := ParseDirectory(tt.dir)
|
||||
// if (err != nil) != tt.wantErr {
|
||||
// t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// ts, err := GenerateModels(context)
|
||||
// require.NoError(t, err)
|
||||
// require.Equal(t, tt.want, string(ts))
|
||||
//
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"log"
|
||||
|
||||
"github.com/wailsapp/wails/v3/internal/parser/testdata/struct_literal_multiple_other/services"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// GreetService is great
|
||||
type GreetService struct {
|
||||
SomeVariable int
|
||||
lowerCase string
|
||||
}
|
||||
|
||||
// Greet does XYZ
|
||||
func (*GreetService) Greet(name string) string {
|
||||
return "Hello " + name
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Bind: []interface{}{
|
||||
&GreetService{},
|
||||
&services.OtherService{},
|
||||
},
|
||||
})
|
||||
|
||||
app.NewWebviewWindow()
|
||||
|
||||
err := app.Run()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package services
|
||||
|
||||
// OtherService is a struct
|
||||
// that does things
|
||||
type OtherService struct {
|
||||
t int
|
||||
}
|
||||
|
||||
// Yay does this and that
|
||||
func (o *OtherService) Yay() {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user