mirror of
https://github.com/taigrr/wails.git
synced 2026-04-14 19:01:09 -07:00
Refactor of parser
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ v2/test/hidden/icon.png
|
||||
v2/internal/ffenestri/runtime.c
|
||||
v2/internal/runtime/assets/desktop.js
|
||||
v2/test/kitchensink/frontend/public/bundle.*
|
||||
v2/pkg/parser/testproject/frontend/wails
|
||||
|
||||
22
v2/cmd/wails/internal/commands/generate/generate.go
Normal file
22
v2/cmd/wails/internal/commands/generate/generate.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package generate
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/leaanthony/clir"
|
||||
"github.com/wailsapp/wails/v2/pkg/parser"
|
||||
)
|
||||
|
||||
// AddSubcommand adds the `dev` command for the Wails application
|
||||
func AddSubcommand(app *clir.Cli, w io.Writer) error {
|
||||
|
||||
command := app.NewSubCommand("generate", "Code Generation Tools")
|
||||
|
||||
// Backend API
|
||||
backendAPI := command.NewSubCommand("api", "Generates a JS module for the frontend to interface with the backend")
|
||||
|
||||
backendAPI.Action(func() error {
|
||||
return parser.GenerateWailsFrontendPackage()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/build"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/dev"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/doctor"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/generate"
|
||||
"github.com/wailsapp/wails/v2/cmd/wails/internal/commands/initialise"
|
||||
)
|
||||
|
||||
@@ -37,6 +38,11 @@ func main() {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = generate.AddSubcommand(app, os.Stdout)
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
||||
err = app.Run()
|
||||
if err != nil {
|
||||
println("\n\nERROR: " + err.Error())
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/wailsapp/wails/v2/internal/project"
|
||||
"github.com/wailsapp/wails/v2/pkg/clilogger"
|
||||
"github.com/wailsapp/wails/v2/pkg/commands/build/internal/backendjs"
|
||||
"github.com/wailsapp/wails/v2/pkg/parser"
|
||||
)
|
||||
|
||||
// Mode is the type used to indicate the build modes
|
||||
@@ -92,7 +92,7 @@ func Build(options *Options) (string, error) {
|
||||
|
||||
// Generate Frontend JS Package
|
||||
outputLogger.Println(" - Generating Backend JS Package")
|
||||
err = backendjs.GenerateBackendJSPackage()
|
||||
err = parser.GenerateWailsFrontendPackage()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt, pkg *Package) {
|
||||
for _, rhs := range assignStmt.Rhs {
|
||||
ce, ok := rhs.(*ast.CallExpr)
|
||||
if ok {
|
||||
se, ok := ce.Fun.(*ast.SelectorExpr)
|
||||
if ok {
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if ok {
|
||||
// Have we found the wails package name?
|
||||
if i.Name == p.wailsPackageVariable {
|
||||
// Check we are calling a function to create the app
|
||||
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Found the app variable name
|
||||
p.applicationVariable = i.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check for function assignment
|
||||
// a := newMyStruct()
|
||||
fe, ok := ce.Fun.(*ast.Ident)
|
||||
if ok {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Store the variable -> Function mapping
|
||||
// so we can later resolve the type
|
||||
pkg.variablesThatWereAssignedByFunctions[i.Name] = fe.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check for literal assignment of struct
|
||||
// EG: myvar := MyStruct{}
|
||||
ue, ok := rhs.(*ast.UnaryExpr)
|
||||
if ok {
|
||||
cl, ok := ue.X.(*ast.CompositeLit)
|
||||
if ok {
|
||||
t, ok := cl.Type.(*ast.Ident)
|
||||
if ok {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
pkg.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
e, ok := cl.Type.(*ast.SelectorExpr)
|
||||
if ok {
|
||||
var thisType = ""
|
||||
var thisPackage = ""
|
||||
switch x := e.X.(type) {
|
||||
case *ast.Ident:
|
||||
thisPackage = x.Name
|
||||
}
|
||||
thisType = e.Sel.Name
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
sn := &StructName{
|
||||
Name: thisType,
|
||||
Package: thisPackage,
|
||||
}
|
||||
pkg.variablesThatWereAssignedByExternalStructLiterals[i.Name] = sn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cl, ok := rhs.(*ast.CompositeLit)
|
||||
if ok {
|
||||
t, ok := cl.Type.(*ast.Ident)
|
||||
if ok {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
pkg.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name
|
||||
} else {
|
||||
println("herer")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println("herer")
|
||||
}
|
||||
} else {
|
||||
println("herer")
|
||||
spew.Dump(rhs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// GenerateBackendJSPackage will generate a Javascript/Typescript
|
||||
// package in `<project>/frontend/backend` that defines which methods
|
||||
// and structs are bound to your frontend
|
||||
func GenerateBackendJSPackage() error {
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := NewParser()
|
||||
|
||||
err = p.parseProject(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.generateModule()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) parseProject(projectPath string) error {
|
||||
mode := packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedSyntax |
|
||||
packages.NeedTypes |
|
||||
packages.NeedImports |
|
||||
packages.NeedTypesInfo
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
cfg := &packages.Config{Fset: fset, Mode: mode, Dir: projectPath}
|
||||
pkgs, err := packages.Load(cfg, "./...")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Problem loading packages")
|
||||
}
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
return errors.Wrap(err, "Errors during parsing")
|
||||
}
|
||||
for _, pkg := range pkgs {
|
||||
parsedPackage, err := p.parsePackage(pkg, fset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Packages[parsedPackage.Name] = parsedPackage
|
||||
}
|
||||
|
||||
// Resolve all the loose ends from parsing
|
||||
err = p.resolve()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) generateModule() error {
|
||||
|
||||
moduleDir, err := createBackendJSDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pkg := range p.Packages {
|
||||
|
||||
// We should only output packages that need generating
|
||||
if !pkg.ShouldBeGenerated() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate directory
|
||||
dir := filepath.Join(moduleDir, pkg.Name)
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
fs.Mkdir(dir)
|
||||
|
||||
err := generatePackage(pkg, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBackendJSDirectory() (string, error) {
|
||||
|
||||
// Calculate the package directory
|
||||
// Note this is *always* called from the project directory
|
||||
// so using paths relative to CWD is fine
|
||||
dir, err := fs.RelativeToCwd("./frontend/backend")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error creating backend js directory")
|
||||
}
|
||||
|
||||
// Remove directory if it exists - REGENERATION!
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error removing module directory")
|
||||
}
|
||||
|
||||
// Make the directory
|
||||
err = fs.Mkdir(dir)
|
||||
|
||||
return dir, err
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func (p *Parser) parseCallExpressions(x *ast.CallExpr, pkg *Package) {
|
||||
f, ok := x.Fun.(*ast.SelectorExpr)
|
||||
if ok {
|
||||
n, ok := f.X.(*ast.Ident)
|
||||
if ok {
|
||||
//Check this is the Bind() call associated with the app variable
|
||||
if n.Name == p.applicationVariable && f.Sel.Name == "Bind" {
|
||||
if len(x.Args) == 1 {
|
||||
ce, ok := x.Args[0].(*ast.CallExpr)
|
||||
if ok {
|
||||
fn, ok := ce.Fun.(*ast.Ident)
|
||||
if ok {
|
||||
pkg.structMethodsThatWereBound.Add(fn.Name)
|
||||
}
|
||||
} else {
|
||||
// We also want to check for Bind( &MyStruct{} )
|
||||
ue, ok := x.Args[0].(*ast.UnaryExpr)
|
||||
if ok {
|
||||
if ue.Op.String() == "&" {
|
||||
cl, ok := ue.X.(*ast.CompositeLit)
|
||||
if ok {
|
||||
t, ok := cl.Type.(*ast.Ident)
|
||||
if ok {
|
||||
pkg.structPointerLiteralsThatWereBound.Add(t.Name)
|
||||
} else {
|
||||
e, ok := cl.Type.(*ast.SelectorExpr)
|
||||
if ok {
|
||||
var thisType = ""
|
||||
var thisPackage = ""
|
||||
switch x := e.X.(type) {
|
||||
case *ast.Ident:
|
||||
thisPackage = x.Name
|
||||
default:
|
||||
println("Identifier in binding not supported:")
|
||||
spew.Dump(ue.X)
|
||||
return
|
||||
}
|
||||
thisType = e.Sel.Name
|
||||
// Save this reference in the package
|
||||
pkg := p.getOrCreatePackage(thisPackage)
|
||||
pkg.structPointerLiteralsThatWereBound.Add(thisType)
|
||||
} else {
|
||||
println("Binding not supported:")
|
||||
spew.Dump(ue.X)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Let's check when the user binds a struct,
|
||||
// rather than a struct pointer: Bind( MyStruct{} )
|
||||
// We do this to provide better hints to the user
|
||||
cl, ok := x.Args[0].(*ast.CompositeLit)
|
||||
if ok {
|
||||
t, ok := cl.Type.(*ast.Ident)
|
||||
if ok {
|
||||
pkg.structLiteralsThatWereBound.Add(t.Name)
|
||||
|
||||
}
|
||||
} else {
|
||||
// Also check for when we bind a variable
|
||||
// myVariable := &MyStruct{}
|
||||
// app.Bind( myVariable )
|
||||
i, ok := x.Args[0].(*ast.Ident)
|
||||
if ok {
|
||||
pkg.variablesThatWereBound.Add(i.Name)
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseComments(comments *ast.CommentGroup) []string {
|
||||
var result []string
|
||||
|
||||
if comments == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
for _, comment := range comments.List {
|
||||
commentText := strings.TrimPrefix(comment.Text, "//")
|
||||
result = append(result, commentText)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func (p *Parser) parseField(field *ast.Field, strct *Struct, pkg *Package) (string, *StructName) {
|
||||
var structName *StructName
|
||||
var fieldType string
|
||||
switch t := field.Type.(type) {
|
||||
case *ast.Ident:
|
||||
fieldType = t.Name
|
||||
case *ast.StarExpr:
|
||||
fieldType = "struct"
|
||||
structName = p.parseStructNameFromStarExpr(t)
|
||||
|
||||
// Save external reference if we have it
|
||||
if structName.Package == "" {
|
||||
// pkg.packageReferences.AddUnique(structName.Package)
|
||||
pkg.structsUsedAsData.AddUnique(structName.Name)
|
||||
} else {
|
||||
// Save this reference to the external struct
|
||||
referencedPackage := p.Packages[structName.Package]
|
||||
if referencedPackage == nil {
|
||||
// TODO: Move this to a global reference list instead of bombing out
|
||||
println("WARNING: Unknown package referenced by field:", structName.Package)
|
||||
}
|
||||
referencedPackage.structsUsedAsData.AddUnique(structName.Name)
|
||||
strct.packageReferences.AddUnique(structName.Package)
|
||||
}
|
||||
|
||||
default:
|
||||
spew.Dump(field)
|
||||
println("Unhandled Field type")
|
||||
os.Exit(1)
|
||||
}
|
||||
return fieldType, structName
|
||||
}
|
||||
|
||||
func (p *Parser) parseFunctionDeclaration(funcDecl *ast.FuncDecl, pkg *Package) {
|
||||
if funcDecl.Recv != nil {
|
||||
// This is a struct method
|
||||
for _, field := range funcDecl.Recv.List {
|
||||
se, ok := field.Type.(*ast.StarExpr)
|
||||
if ok {
|
||||
// This is a struct pointer method
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if ok {
|
||||
// We want to ignore Internal functions
|
||||
if p.internalMethods.Contains(funcDecl.Name.Name) {
|
||||
continue
|
||||
}
|
||||
// If we haven't already found this struct,
|
||||
// Create a placeholder in the cache
|
||||
parsedStruct := pkg.Structs[i.Name]
|
||||
if parsedStruct == nil {
|
||||
pkg.Structs[i.Name] = &Struct{
|
||||
Name: i.Name,
|
||||
}
|
||||
parsedStruct = pkg.Structs[i.Name]
|
||||
}
|
||||
|
||||
// If this method is Public
|
||||
if string(funcDecl.Name.Name[0]) == strings.ToUpper((string(funcDecl.Name.Name[0]))) {
|
||||
structMethod := &Method{
|
||||
Name: funcDecl.Name.Name,
|
||||
}
|
||||
// Check if the method has comments.
|
||||
// If so, save it with the parsed method
|
||||
if funcDecl.Doc != nil {
|
||||
structMethod.Comments = p.parseComments(funcDecl.Doc)
|
||||
}
|
||||
|
||||
// Save the input parameters
|
||||
if funcDecl.Type.Params != nil {
|
||||
for _, inputField := range funcDecl.Type.Params.List {
|
||||
fieldType, structName := p.parseField(inputField, parsedStruct, pkg)
|
||||
for _, name := range inputField.Names {
|
||||
structMethod.Inputs = append(structMethod.Inputs, &Field{
|
||||
Name: name.Name,
|
||||
Type: fieldType,
|
||||
Struct: structName,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the output parameters
|
||||
if funcDecl.Type.Results != nil {
|
||||
for _, outputField := range funcDecl.Type.Results.List {
|
||||
fieldType, structName := p.parseField(outputField, parsedStruct, pkg)
|
||||
if len(outputField.Names) == 0 {
|
||||
structMethod.Returns = append(structMethod.Returns, &Field{
|
||||
Type: fieldType,
|
||||
Struct: structName,
|
||||
})
|
||||
} else {
|
||||
for _, name := range outputField.Names {
|
||||
structMethod.Returns = append(structMethod.Returns, &Field{
|
||||
Name: name.Name,
|
||||
Type: fieldType,
|
||||
Struct: structName,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append this method to the parsed struct
|
||||
parsedStruct.Methods = append(parsedStruct.Methods, structMethod)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a function declaration
|
||||
// We care about its name and return type
|
||||
// This will allow us to resolve types later
|
||||
functionName := funcDecl.Name.Name
|
||||
|
||||
// Look for one that returns a single value
|
||||
if funcDecl.Type != nil && funcDecl.Type.Results != nil && funcDecl.Type.Results.List != nil {
|
||||
if len(funcDecl.Type.Results.List) == 1 {
|
||||
// Check for *struct
|
||||
t, ok := funcDecl.Type.Results.List[0].Type.(*ast.StarExpr)
|
||||
if ok {
|
||||
s, ok := t.X.(*ast.Ident)
|
||||
if ok {
|
||||
// println("*** Function", functionName, "found which returns: *"+s.Name)
|
||||
pkg.functionsThatReturnStructPointers[functionName] = s.Name
|
||||
}
|
||||
} else {
|
||||
// Check for functions that return a struct
|
||||
// This is to help us provide hints if the user binds a struct
|
||||
t, ok := funcDecl.Type.Results.List[0].Type.(*ast.Ident)
|
||||
if ok {
|
||||
// println("*** Function", functionName, "found which returns: "+t.Name)
|
||||
pkg.functionsThatReturnStructs[functionName] = t.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Package defines a parsed package
|
||||
type Package struct {
|
||||
Name string
|
||||
Structs map[string]*Struct
|
||||
|
||||
// These are the structs declared in this package
|
||||
// that are used as data by either this or other packages
|
||||
structsUsedAsData slicer.StringSlicer
|
||||
|
||||
// A list of functions that return struct pointers
|
||||
functionsThatReturnStructPointers map[string]string
|
||||
|
||||
// A list of functions that return structs
|
||||
functionsThatReturnStructs map[string]string
|
||||
|
||||
// A list of struct literals that were bound to the application
|
||||
// EG: app.Bind( &mystruct{} )
|
||||
structLiteralsThatWereBound slicer.StringSlicer
|
||||
|
||||
// A list of struct pointer literals that were bound to the application
|
||||
// EG: app.Bind( &mystruct{} )
|
||||
structPointerLiteralsThatWereBound slicer.StringSlicer
|
||||
|
||||
// A list of methods that returns structs to the Bind method
|
||||
// EG: app.Bind( newMyStruct() )
|
||||
structMethodsThatWereBound slicer.StringSlicer
|
||||
|
||||
// A list of variables that were used for binding
|
||||
// Eg: myVar := &mystruct{}; app.Bind( myVar )
|
||||
variablesThatWereBound slicer.StringSlicer
|
||||
|
||||
// A list of variables that were assigned using a function call
|
||||
// EG: myVar := newStruct()
|
||||
variablesThatWereAssignedByFunctions map[string]string
|
||||
|
||||
// A map of variables that were assigned using a struct literal
|
||||
// EG: myVar := MyStruct{}
|
||||
variablesThatWereAssignedByStructLiterals map[string]string
|
||||
|
||||
// A map of variables that were assigned using a struct literal
|
||||
// in a different package
|
||||
// EG: myVar := mypackage.MyStruct{}
|
||||
variablesThatWereAssignedByExternalStructLiterals map[string]*StructName
|
||||
}
|
||||
|
||||
func newPackage(name string) *Package {
|
||||
return &Package{
|
||||
Name: name,
|
||||
Structs: make(map[string]*Struct),
|
||||
functionsThatReturnStructPointers: make(map[string]string),
|
||||
functionsThatReturnStructs: make(map[string]string),
|
||||
variablesThatWereAssignedByFunctions: make(map[string]string),
|
||||
variablesThatWereAssignedByStructLiterals: make(map[string]string),
|
||||
variablesThatWereAssignedByExternalStructLiterals: make(map[string]*StructName),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) parsePackage(pkg *packages.Package, fset *token.FileSet) (*Package, error) {
|
||||
result := p.Packages[pkg.Name]
|
||||
if result == nil {
|
||||
result = newPackage(pkg.Name)
|
||||
}
|
||||
|
||||
// Get the absolute path to the project's main.go file
|
||||
pathToMain, err := fs.RelativeToCwd("main.go")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Work out if this is the main package
|
||||
goFiles := slicer.String(pkg.GoFiles)
|
||||
if goFiles.Contains(pathToMain) {
|
||||
// This is the program entrypoint file
|
||||
// Scan the imports for the wails v2 import
|
||||
for key, details := range pkg.Imports {
|
||||
if key == "github.com/wailsapp/wails/v2" {
|
||||
p.wailsPackageVariable = details.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileAst := range pkg.Syntax {
|
||||
var parseError error
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
// if typeDecl, ok := n.(*ast.TypeSpec); ok {
|
||||
// // Parse struct definitions
|
||||
// if structType, ok := typeDecl.Type.(*ast.StructType); ok {
|
||||
// structName := typeDecl.Name.Name
|
||||
// // findInFields(structTy.Fields, n, pkg.TypesInfo, fset)
|
||||
// structDef, err := p.ParseStruct(structType, structName, result)
|
||||
// if err != nil {
|
||||
// parseError = err
|
||||
// return false
|
||||
// }
|
||||
|
||||
// // Parse comments
|
||||
// structDef.Comments = p.parseComments(typeDecl.Doc)
|
||||
|
||||
// result.Structs[structName] = structDef
|
||||
// }
|
||||
// }
|
||||
|
||||
if genDecl, ok := n.(*ast.GenDecl); ok {
|
||||
println("GenDecl:")
|
||||
for _, spec := range genDecl.Specs {
|
||||
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
||||
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||
structName := typeSpec.Name.Name
|
||||
structDef, err := p.ParseStruct(structType, structName, result)
|
||||
if err != nil {
|
||||
parseError = err
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse comments
|
||||
structDef.Comments = p.parseComments(genDecl.Doc)
|
||||
|
||||
result.Structs[structName] = structDef
|
||||
}
|
||||
}
|
||||
}
|
||||
spew.Dump(genDecl)
|
||||
}
|
||||
|
||||
// Capture call expressions
|
||||
if callExpr, ok := n.(*ast.CallExpr); ok {
|
||||
p.parseCallExpressions(callExpr, result)
|
||||
}
|
||||
|
||||
// Parse Assignments
|
||||
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
||||
p.parseAssignment(assignStmt, result)
|
||||
}
|
||||
|
||||
// Parse Function declarations
|
||||
if funcDecl, ok := n.(*ast.FuncDecl); ok {
|
||||
p.parseFunctionDeclaration(funcDecl, result)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
if parseError != nil {
|
||||
return nil, parseError
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func generatePackage(pkg *Package, moduledir string) error {
|
||||
|
||||
// Get path to local file
|
||||
typescriptTemplateFile := fs.RelativePath("./package.d.template")
|
||||
|
||||
// Load typescript template
|
||||
typescriptTemplateData := fs.MustLoadString(typescriptTemplateFile)
|
||||
typescriptTemplate, err := template.New("typescript").Parse(typescriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
}
|
||||
|
||||
// Execute javascript template
|
||||
var buffer bytes.Buffer
|
||||
err = typescriptTemplate.Execute(&buffer, pkg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save typescript file
|
||||
err = ioutil.WriteFile(filepath.Join(moduledir, "index.d.ts"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
// Get path to local file
|
||||
javascriptTemplateFile := fs.RelativePath("./package.template")
|
||||
|
||||
// Load javascript template
|
||||
javascriptTemplateData := fs.MustLoadString(javascriptTemplateFile)
|
||||
javascriptTemplate, err := template.New("javascript").Parse(javascriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
}
|
||||
|
||||
// Reset the buffer
|
||||
buffer.Reset()
|
||||
|
||||
err = javascriptTemplate.Execute(&buffer, pkg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save javascript file
|
||||
err = ioutil.WriteFile(filepath.Join(moduledir, "index.js"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeclarationReferences returns the typescript declaration references for the package
|
||||
func (p *Package) DeclarationReferences() []string {
|
||||
var result []string
|
||||
for _, strct := range p.Structs {
|
||||
if strct.IsBound {
|
||||
refs := strct.packageReferences.AsSlice()
|
||||
result = append(result, refs...)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// StructIsUsedAsData returns true if the given struct name has
|
||||
// been used in structs, inputs or outputs by other packages
|
||||
func (p *Package) StructIsUsedAsData(structName string) bool {
|
||||
return p.structsUsedAsData.Contains(structName)
|
||||
}
|
||||
|
||||
func (p *Package) resolveBoundStructLiterals() {
|
||||
p.structLiteralsThatWereBound.Each(func(structName string) {
|
||||
strct := p.Structs[structName]
|
||||
if strct == nil {
|
||||
println("Warning: Cannot find bound struct", structName, "in package", p.Name)
|
||||
return
|
||||
}
|
||||
println("Bound struct", strct.Name, "in package", p.Name)
|
||||
strct.IsBound = true
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Package) resolveBoundStructPointerLiterals() {
|
||||
p.structPointerLiteralsThatWereBound.Each(func(structName string) {
|
||||
strct := p.Structs[structName]
|
||||
if strct == nil {
|
||||
println("Warning: Cannot find bound struct", structName, "in package", p.Name)
|
||||
return
|
||||
}
|
||||
println("Bound struct pointer", strct.Name, "in package", p.Name)
|
||||
strct.IsBound = true
|
||||
})
|
||||
}
|
||||
|
||||
// ShouldBeGenerated indicates if the package should be generated
|
||||
// The package should be generated only if we have structs that are
|
||||
// bound or structs that are used as data
|
||||
func (p *Package) ShouldBeGenerated() bool {
|
||||
for _, strct := range p.Structs {
|
||||
if strct.IsBound || strct.IsUsedAsData {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
// Parser is our Wails project parser
|
||||
type Parser struct {
|
||||
Packages map[string]*Package
|
||||
|
||||
// The variable used to store the Wails application
|
||||
// EG: app := wails.CreateApp()
|
||||
applicationVariable string
|
||||
|
||||
// The name of the wails package that's imported
|
||||
// import "github.com/wailsapp/wails/v2" -> wails
|
||||
// import mywails "github.com/wailsapp/wails/v2" -> mywails
|
||||
wailsPackageVariable string
|
||||
|
||||
// Internal methods (WailsInit/WailsShutdown)
|
||||
internalMethods *slicer.StringSlicer
|
||||
}
|
||||
|
||||
// NewParser creates a new Wails Project parser
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
Packages: make(map[string]*Package),
|
||||
internalMethods: slicer.String([]string{"WailsInit", "WailsShutdown"}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) resolve() error {
|
||||
|
||||
// Resolve bound structs
|
||||
err := p.resolveBoundStructs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) resolveBoundStructs() error {
|
||||
|
||||
// Resolve Struct Literals
|
||||
p.resolveBoundStructLiterals()
|
||||
|
||||
// Resolve Struct Pointer Literals
|
||||
p.resolveBoundStructPointerLiterals()
|
||||
|
||||
// Resolve functions that were bound
|
||||
// EG: app.Bind( newBasic() )
|
||||
p.resolveBoundFunctions()
|
||||
|
||||
// Resolve variables that were bound
|
||||
p.resolveBoundVariables()
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *Parser) resolveBoundStructLiterals() {
|
||||
|
||||
// Resolve struct literals in each package
|
||||
for _, pkg := range p.Packages {
|
||||
pkg.resolveBoundStructLiterals()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) resolveBoundStructPointerLiterals() {
|
||||
|
||||
// Resolve struct pointer literals
|
||||
for _, pkg := range p.Packages {
|
||||
pkg.resolveBoundStructPointerLiterals()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) resolveBoundFunctions() {
|
||||
|
||||
// Loop over packages
|
||||
for _, pkg := range p.Packages {
|
||||
|
||||
// Iterate over the method names
|
||||
pkg.structMethodsThatWereBound.Each(func(functionName string) {
|
||||
// Resolve each method name
|
||||
structName := p.resolveFunctionReturnType(pkg, functionName)
|
||||
|
||||
strct := pkg.Structs[structName]
|
||||
if strct == nil {
|
||||
println("WARNING: Unable to find definition for struct", structName)
|
||||
return
|
||||
}
|
||||
strct.IsBound = true
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// resolveFunctionReturnType gets the return type for the given package/function name combination
|
||||
func (p *Parser) resolveFunctionReturnType(pkg *Package, functionName string) string {
|
||||
structName := pkg.functionsThatReturnStructPointers[functionName]
|
||||
if structName == "" {
|
||||
structName = pkg.functionsThatReturnStructs[functionName]
|
||||
}
|
||||
if structName == "" {
|
||||
println("WARNING: Unable to resolve bound function", functionName, "in package", pkg.Name)
|
||||
}
|
||||
return structName
|
||||
}
|
||||
|
||||
func (p *Parser) markStructAsBound(pkg *Package, structName string) {
|
||||
strct := pkg.Structs[structName]
|
||||
if strct == nil {
|
||||
println("WARNING: Unable to find definition for struct", structName)
|
||||
}
|
||||
println("Found bound struct:", strct.Name)
|
||||
strct.IsBound = true
|
||||
}
|
||||
|
||||
func (p *Parser) resolveBoundVariables() {
|
||||
|
||||
for _, pkg := range p.Packages {
|
||||
|
||||
// Iterate over the method names
|
||||
pkg.variablesThatWereBound.Each(func(variableName string) {
|
||||
println("Resolving variable: ", variableName)
|
||||
|
||||
var structName string
|
||||
|
||||
// Resolve each method name
|
||||
funcName := pkg.variablesThatWereAssignedByFunctions[variableName]
|
||||
if funcName != "" {
|
||||
// Found function name - resolve Function return type
|
||||
structName = p.resolveFunctionReturnType(pkg, funcName)
|
||||
}
|
||||
|
||||
// If we couldn't resolve to a function, then let's try struct literals
|
||||
if structName == "" {
|
||||
funcName = pkg.variablesThatWereAssignedByStructLiterals[variableName]
|
||||
if funcName != "" {
|
||||
// Found function name - resolve Function return type
|
||||
structName = p.resolveFunctionReturnType(pkg, funcName)
|
||||
}
|
||||
}
|
||||
|
||||
// Look for the variable as an external literal reference
|
||||
if structName == "" {
|
||||
sn := pkg.variablesThatWereAssignedByExternalStructLiterals[variableName]
|
||||
if sn != nil {
|
||||
pkg := p.Packages[sn.Package]
|
||||
if pkg != nil {
|
||||
p.markStructAsBound(pkg, sn.Name)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if structName == "" {
|
||||
println("WARNING: Unable to resolve bound variable", variableName, "in package", pkg.Name)
|
||||
return
|
||||
}
|
||||
|
||||
p.markStructAsBound(pkg, structName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) bindStructByStructName(sn *StructName) {
|
||||
// Get package
|
||||
pkg := p.Packages[sn.Package]
|
||||
if pkg == nil {
|
||||
// Ignore, it will get picked up by the compiler
|
||||
return
|
||||
}
|
||||
|
||||
strct := pkg.Structs[sn.Name]
|
||||
if strct == nil {
|
||||
// Ignore, it will get picked up by the compiler
|
||||
return
|
||||
}
|
||||
|
||||
println("Found bound Struct:", sn.ToString())
|
||||
strct.IsBound = true
|
||||
}
|
||||
|
||||
func (p *Parser) getOrCreatePackage(name string) *Package {
|
||||
result := p.Packages[name]
|
||||
if result == nil {
|
||||
result = newPackage(name)
|
||||
p.Packages[name] = result
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
// Struct defines a parsed struct
|
||||
type Struct struct {
|
||||
Name string
|
||||
Comments []string
|
||||
Fields []*Field
|
||||
Methods []*Method
|
||||
IsBound bool
|
||||
|
||||
// This indicates that the struct is passed as data
|
||||
// between the frontend and backend
|
||||
IsUsedAsData bool
|
||||
|
||||
// These are references to other packages
|
||||
packageReferences slicer.StringSlicer
|
||||
}
|
||||
|
||||
// StructName is used to define a fully qualified struct name
|
||||
// EG: mypackage.Person
|
||||
type StructName struct {
|
||||
Name string
|
||||
Package string
|
||||
}
|
||||
|
||||
// ToString returns a text representation of the struct anme
|
||||
func (s *StructName) ToString() string {
|
||||
result := ""
|
||||
if s.Package != "" {
|
||||
result = s.Package + "."
|
||||
}
|
||||
return result + s.Name
|
||||
}
|
||||
|
||||
// Field defines a parsed struct field
|
||||
type Field struct {
|
||||
Name string
|
||||
Type string
|
||||
Struct *StructName
|
||||
Comments []string
|
||||
}
|
||||
|
||||
// JSType returns the Javascript type for this field
|
||||
func (f *Field) JSType() string {
|
||||
return string(goTypeToJS(f))
|
||||
}
|
||||
|
||||
// TypeAsTSType converts the Field type to something TS wants
|
||||
func (f *Field) TypeAsTSType() string {
|
||||
var result = ""
|
||||
switch f.Type {
|
||||
case "string":
|
||||
result = "string"
|
||||
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
|
||||
result = "number"
|
||||
case "float32", "float64":
|
||||
result = "number"
|
||||
case "bool":
|
||||
result = "boolean"
|
||||
case "struct":
|
||||
if f.Struct.Package != "" {
|
||||
result = f.Struct.Package + "."
|
||||
}
|
||||
result = result + f.Struct.Name
|
||||
default:
|
||||
result = "any"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Parser) ParseStruct(structType *ast.StructType, name string, pkg *Package) (*Struct, error) {
|
||||
|
||||
// Check if we've seen this struct before
|
||||
result := pkg.Structs[name]
|
||||
|
||||
// If we haven't create a new one
|
||||
if result == nil {
|
||||
result = &Struct{Name: name}
|
||||
}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
result.Fields = append(result.Fields, p.ParseField(field, result, pkg)...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseStructNameFromStarExpr(starExpr *ast.StarExpr) *StructName {
|
||||
pkg := ""
|
||||
name := ""
|
||||
// Determine the FQN
|
||||
switch x := starExpr.X.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
switch i := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
pkg = i.Name
|
||||
default:
|
||||
println("one")
|
||||
fieldNotSupported(x)
|
||||
}
|
||||
|
||||
name = x.Sel.Name
|
||||
|
||||
case *ast.StarExpr:
|
||||
switch s := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
name = s.Name
|
||||
default:
|
||||
println("two")
|
||||
fieldNotSupported(x)
|
||||
}
|
||||
case *ast.Ident:
|
||||
name = x.Name
|
||||
default:
|
||||
println("three")
|
||||
|
||||
fieldNotSupported(x)
|
||||
}
|
||||
return &StructName{
|
||||
Name: name,
|
||||
Package: pkg,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) ParseField(field *ast.Field, strct *Struct, pkg *Package) []*Field {
|
||||
var result []*Field
|
||||
|
||||
var fieldType string
|
||||
var structName *StructName
|
||||
// Determine type
|
||||
switch t := field.Type.(type) {
|
||||
case *ast.Ident:
|
||||
fieldType = t.Name
|
||||
case *ast.StarExpr:
|
||||
fieldType = "struct"
|
||||
structName = p.parseStructNameFromStarExpr(t)
|
||||
// Save external reference if we have it
|
||||
if structName.Package == "" {
|
||||
pkg.structsUsedAsData.AddUnique(structName.Name)
|
||||
} else {
|
||||
// Save this reference to the external struct
|
||||
referencedPackage := p.Packages[structName.Package]
|
||||
if referencedPackage == nil {
|
||||
// Should we be ignoring this?
|
||||
} else {
|
||||
strct.packageReferences.AddUnique(structName.Package)
|
||||
referencedPackage.structsUsedAsData.AddUnique(structName.Name)
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
fieldNotSupported(t)
|
||||
}
|
||||
|
||||
// Loop over names
|
||||
for _, name := range field.Names {
|
||||
|
||||
// Create a field per name
|
||||
thisField := &Field{
|
||||
Comments: p.parseComments(field.Doc),
|
||||
}
|
||||
thisField.Name = name.Name
|
||||
thisField.Type = fieldType
|
||||
thisField.Struct = structName
|
||||
|
||||
result = append(result, thisField)
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func fieldNotSupported(t interface{}) {
|
||||
println("Field type not supported:")
|
||||
spew.Dump(t)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Method defines a struct method
|
||||
type Method struct {
|
||||
Name string
|
||||
Comments []string
|
||||
Inputs []*Field
|
||||
Returns []*Field
|
||||
}
|
||||
|
||||
// InputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Typescript
|
||||
func (m *Method) InputsAsTSText() string {
|
||||
var inputs []string
|
||||
|
||||
for _, input := range m.Inputs {
|
||||
inputText := fmt.Sprintf("%s: %s", input.Name, goTypeToTS(input))
|
||||
inputs = append(inputs, inputText)
|
||||
}
|
||||
|
||||
return strings.Join(inputs, ", ")
|
||||
}
|
||||
|
||||
// OutputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *Method) OutputsAsTSText() string {
|
||||
|
||||
if len(m.Returns) == 0 {
|
||||
return "void"
|
||||
}
|
||||
|
||||
var result []string
|
||||
|
||||
for _, output := range m.Returns {
|
||||
result = append(result, goTypeToTS(output))
|
||||
}
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
// InputsAsJSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *Method) InputsAsJSText() string {
|
||||
var inputs []string
|
||||
|
||||
for _, input := range m.Inputs {
|
||||
inputs = append(inputs, input.Name)
|
||||
}
|
||||
|
||||
return strings.Join(inputs, ", ")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func (p *Parser) getApplicationVariableName(pkg *packages.Package, wailsImportName string) (string, bool) {
|
||||
|
||||
var applicationVariableName = ""
|
||||
|
||||
// Iterate through the whole package looking for the application name
|
||||
for _, fileAst := range pkg.Syntax {
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
// Parse Assignments looking for application name
|
||||
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
||||
|
||||
// Check the RHS is of the form:
|
||||
// `app := wails.CreateApp()` or
|
||||
// `app := wails.CreateAppWithOptions`
|
||||
for _, rhs := range assignStmt.Rhs {
|
||||
ce, ok := rhs.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
se, ok := ce.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Have we found the wails import name?
|
||||
if i.Name == wailsImportName {
|
||||
// Check we are calling a function to create the app
|
||||
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Found the app variable name
|
||||
applicationVariableName = i.Name
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return applicationVariableName, applicationVariableName != ""
|
||||
}
|
||||
50
v2/pkg/parser/applicationVariableName.go
Normal file
50
v2/pkg/parser/applicationVariableName.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package parser
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func (p *Package) getApplicationVariableName(file *ast.File, wailsImportName string) string {
|
||||
|
||||
// Iterate through the whole file looking for the application name
|
||||
applicationVariableName := ""
|
||||
|
||||
// Inspect the file
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
// Parse Assignments looking for application name
|
||||
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
||||
|
||||
// Check the RHS is of the form:
|
||||
// `app := wails.CreateApp()` or
|
||||
// `app := wails.CreateAppWithOptions`
|
||||
for _, rhs := range assignStmt.Rhs {
|
||||
ce, ok := rhs.(*ast.CallExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
se, ok := ce.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
i, ok := se.X.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Have we found the wails import name?
|
||||
if i.Name == wailsImportName {
|
||||
// Check we are calling a function to create the app
|
||||
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
|
||||
if len(assignStmt.Lhs) == 1 {
|
||||
i, ok := assignStmt.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Found the app variable name
|
||||
applicationVariableName = i.Name
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return applicationVariableName
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseComments(comments *ast.CommentGroup) []string {
|
||||
func parseComments(comments *ast.CommentGroup) []string {
|
||||
var result []string
|
||||
|
||||
if comments == nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package backendjs
|
||||
package parser
|
||||
|
||||
// JSType represents a javascript type
|
||||
type JSType string
|
||||
@@ -35,14 +35,17 @@ func goTypeToJS(input *Field) string {
|
||||
// case reflect.Ptr, reflect.Struct, reflect.Map, reflect.Interface:
|
||||
// return JsObject
|
||||
case "struct":
|
||||
return input.Struct.ToString()
|
||||
return input.Struct.Name
|
||||
default:
|
||||
println("UNSUPPORTED: ", input)
|
||||
return "*"
|
||||
}
|
||||
}
|
||||
|
||||
func goTypeToTS(input *Field) string {
|
||||
// goTypeToTS converts the given field into a Typescript type
|
||||
// The pkgName is the package that the field is being output in.
|
||||
// This is used to ensure we don't qualify local structs.
|
||||
func goTypeToTS(input *Field, pkgName string) string {
|
||||
var result string
|
||||
switch input.Type {
|
||||
case "string":
|
||||
@@ -54,8 +57,10 @@ func goTypeToTS(input *Field) string {
|
||||
case "bool":
|
||||
result = "boolean"
|
||||
case "struct":
|
||||
if input.Struct.Package != "" {
|
||||
result = input.Struct.Package + "."
|
||||
if input.Struct.Package.Name != "" {
|
||||
if input.Struct.Package.Name != pkgName {
|
||||
result = input.Struct.Package.Name + "."
|
||||
}
|
||||
}
|
||||
result += input.Struct.Name
|
||||
// case reflect.Array, reflect.Slice:
|
||||
@@ -70,9 +75,9 @@ func goTypeToTS(input *Field) string {
|
||||
return JsUnsupported
|
||||
}
|
||||
|
||||
// if input.IsArray {
|
||||
// result = "Array<" + result + ">"
|
||||
// }
|
||||
if input.IsArray {
|
||||
result = "Array<" + result + ">"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -3,25 +3,67 @@ package parser
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// Field defines a parsed struct field
|
||||
type Field struct {
|
||||
Name string
|
||||
Type string
|
||||
Struct *Struct
|
||||
|
||||
// Name of the field
|
||||
Name string
|
||||
|
||||
// The type of the field.
|
||||
// "struct" if it's a struct
|
||||
Type string
|
||||
|
||||
// A pointer to the struct if the Type is "struct"
|
||||
Struct *Struct
|
||||
|
||||
// User comments on the field
|
||||
Comments []string
|
||||
|
||||
// This struct reference is to temporarily hold the name
|
||||
// of the struct during parsing
|
||||
structReference *StructReference
|
||||
// Indicates if the Field is an array of type "Type"
|
||||
IsArray bool
|
||||
}
|
||||
|
||||
func (p *Parser) parseField(field *ast.Field, thisPackageName string) ([]*Field, error) {
|
||||
// JSType returns the Javascript type for this field
|
||||
func (f *Field) JSType() string {
|
||||
return string(goTypeToJS(f))
|
||||
}
|
||||
|
||||
// TypeAsTSType converts the Field type to something TS wants
|
||||
func (f *Field) TypeAsTSType(pkgName string) string {
|
||||
var result = ""
|
||||
switch f.Type {
|
||||
case "string":
|
||||
result = "string"
|
||||
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
|
||||
result = "number"
|
||||
case "float32", "float64":
|
||||
result = "number"
|
||||
case "bool":
|
||||
result = "boolean"
|
||||
case "struct":
|
||||
if f.Struct.Package != nil {
|
||||
if f.Struct.Package.Name != pkgName {
|
||||
result = f.Struct.Package.Name + "."
|
||||
}
|
||||
}
|
||||
result = result + f.Struct.Name
|
||||
default:
|
||||
result = "any"
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*Field, error) {
|
||||
var result []*Field
|
||||
|
||||
var fieldType string
|
||||
var structReference *StructReference
|
||||
var strct *Struct
|
||||
var isArray bool
|
||||
|
||||
// Determine type
|
||||
switch t := field.Type.(type) {
|
||||
@@ -29,20 +71,86 @@ func (p *Parser) parseField(field *ast.Field, thisPackageName string) ([]*Field,
|
||||
fieldType = t.Name
|
||||
case *ast.StarExpr:
|
||||
fieldType = "struct"
|
||||
packageName, structName, err := p.parseStructNameFromStarExpr(t)
|
||||
packageName, structName, err := parseStructNameFromStarExpr(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we don't ahve a package name, it means it's in this package
|
||||
if packageName == "" {
|
||||
packageName = thisPackageName
|
||||
// If this is an external package, find it
|
||||
if packageName != "" {
|
||||
referencedGoPackage := pkg.getImportByName(packageName, file)
|
||||
referencedPackage := p.getPackageByID(referencedGoPackage.ID)
|
||||
|
||||
// If we found the struct, save it as an external package reference
|
||||
if referencedPackage != nil {
|
||||
pkg.addExternalReference(referencedPackage)
|
||||
}
|
||||
|
||||
// We save this to pkg anyway, because we want to know if this package
|
||||
// was NOT found
|
||||
pkg = referencedPackage
|
||||
}
|
||||
|
||||
// Temporarily store the struct reference
|
||||
structReference = newStructReference(packageName, structName)
|
||||
// If this is a package in our project, parse the struct!
|
||||
if pkg != nil {
|
||||
|
||||
// Parse the struct
|
||||
strct, err = p.parseStruct(pkg, structName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the fact this struct is used as a data type
|
||||
strct.IsUsedAsData = true
|
||||
}
|
||||
|
||||
case *ast.ArrayType:
|
||||
isArray = true
|
||||
// Parse the Elt (There must be a better way!)
|
||||
switch t := t.Elt.(type) {
|
||||
case *ast.Ident:
|
||||
fieldType = t.Name
|
||||
case *ast.StarExpr:
|
||||
fieldType = "struct"
|
||||
packageName, structName, err := parseStructNameFromStarExpr(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If this is an external package, find it
|
||||
if packageName != "" {
|
||||
referencedGoPackage := pkg.getImportByName(packageName, file)
|
||||
referencedPackage := p.getPackageByID(referencedGoPackage.ID)
|
||||
|
||||
// If we found the struct, save it as an external package reference
|
||||
if referencedPackage != nil {
|
||||
pkg.addExternalReference(referencedPackage)
|
||||
}
|
||||
|
||||
// We save this to pkg anyway, because we want to know if this package
|
||||
// was NOT found
|
||||
pkg = referencedPackage
|
||||
}
|
||||
|
||||
// If this is a package in our project, parse the struct!
|
||||
if pkg != nil {
|
||||
|
||||
// Parse the struct
|
||||
strct, err = p.parseStruct(pkg, structName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Save the fact this struct is used as a data type
|
||||
strct.IsUsedAsData = true
|
||||
}
|
||||
default:
|
||||
// We will default to "Array<any>" for eg nested arrays
|
||||
fieldType = "any"
|
||||
}
|
||||
|
||||
default:
|
||||
spew.Dump(t)
|
||||
return nil, fmt.Errorf("Unsupported field found in struct: %+v", t)
|
||||
}
|
||||
|
||||
@@ -52,11 +160,12 @@ func (p *Parser) parseField(field *ast.Field, thisPackageName string) ([]*Field,
|
||||
|
||||
// Create a field per name
|
||||
thisField := &Field{
|
||||
Comments: p.parseComments(field.Doc),
|
||||
Comments: parseComments(field.Doc),
|
||||
}
|
||||
thisField.Name = name.Name
|
||||
thisField.Type = fieldType
|
||||
thisField.structReference = structReference
|
||||
thisField.Struct = strct
|
||||
thisField.IsArray = isArray
|
||||
|
||||
result = append(result, thisField)
|
||||
}
|
||||
@@ -65,48 +174,12 @@ func (p *Parser) parseField(field *ast.Field, thisPackageName string) ([]*Field,
|
||||
|
||||
// When we have no name
|
||||
thisField := &Field{
|
||||
Comments: p.parseComments(field.Doc),
|
||||
Comments: parseComments(field.Doc),
|
||||
}
|
||||
thisField.Type = fieldType
|
||||
thisField.structReference = structReference
|
||||
|
||||
thisField.Struct = strct
|
||||
thisField.IsArray = isArray
|
||||
result = append(result, thisField)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) resolveFieldReferences(fields []*Field) error {
|
||||
|
||||
// Loop over fields
|
||||
for _, field := range fields {
|
||||
|
||||
// If we have a struct reference but no actual struct,
|
||||
// we need to resolve it
|
||||
if field.structReference != nil && field.Struct == nil {
|
||||
fqn := field.structReference.FullyQualifiedName()
|
||||
println("Need to resolve struct reference: ", fqn)
|
||||
// Check the cache for the struct
|
||||
structPointer, err := p.ParseStruct(field.structReference.Package, field.structReference.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
field.Struct = structPointer
|
||||
if field.Struct != nil {
|
||||
// Save the fact that the struct is used as data
|
||||
field.Struct.UsedAsData = true
|
||||
println("Resolved struct reference:", fqn)
|
||||
|
||||
// Resolve *its* references
|
||||
err = p.resolveStructReferences(field.Struct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
println("Unable to resolve struct reference:", fqn)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
import "go/ast"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
// findBoundStructs will search through the Wails project looking
|
||||
// for which structs have been bound using the `Bind()` method
|
||||
func (p *Parser) findBoundStructs(pkg *Package) error {
|
||||
|
||||
func (p *Parser) getImportByName(pkg *packages.Package, importName string) *packages.Package {
|
||||
// Find package path
|
||||
for _, imp := range pkg.Imports {
|
||||
if imp.Name == importName {
|
||||
return imp
|
||||
// Iterate through the files in the package looking for the bound structs
|
||||
for _, fileAst := range pkg.gopackage.Syntax {
|
||||
|
||||
// Find the wails import name
|
||||
wailsImportName := pkg.getWailsImportName(fileAst)
|
||||
|
||||
// If this file doesn't import wails, continue
|
||||
if wailsImportName == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) findBoundStructsInPackage(pkg *packages.Package, applicationVariableName string) []*StructReference {
|
||||
applicationVariableName := pkg.getApplicationVariableName(fileAst, wailsImportName)
|
||||
if applicationVariableName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var boundStructs []*StructReference
|
||||
var parseError error
|
||||
|
||||
// Iterate through the whole package looking for the bound structs
|
||||
for _, fileAst := range pkg.Syntax {
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
// Parse Call expressions looking for bind calls
|
||||
callExpr, ok := n.(*ast.CallExpr)
|
||||
@@ -92,8 +94,7 @@ func (p *Parser) findBoundStructsInPackage(pkg *packages.Package, applicationVar
|
||||
|
||||
// app.Bind( &myStruct{} )
|
||||
case *ast.Ident:
|
||||
boundStruct := newStructReference(pkg.Name, boundStructExp.Name)
|
||||
boundStructs = append(boundStructs, boundStruct)
|
||||
pkg.boundStructs.Add(boundStructExp.Name)
|
||||
|
||||
// app.Bind( &mypackage.myStruct{} )
|
||||
case *ast.SelectorExpr:
|
||||
@@ -108,39 +109,39 @@ func (p *Parser) findBoundStructsInPackage(pkg *packages.Package, applicationVar
|
||||
return true
|
||||
}
|
||||
structName = boundStructExp.Sel.Name
|
||||
referencedPackage := p.getImportByName(pkg, packageName)
|
||||
boundStruct := newStructReference(referencedPackage.Name, structName)
|
||||
boundStructs = append(boundStructs, boundStruct)
|
||||
referencedPackage := pkg.getImportByName(packageName, fileAst)
|
||||
packageWrapper := p.getPackageByID(referencedPackage.ID)
|
||||
packageWrapper.boundStructs.Add(structName)
|
||||
}
|
||||
|
||||
// Binding struct literals
|
||||
case *ast.CompositeLit:
|
||||
switch literal := boundItem.Type.(type) {
|
||||
// // Binding struct literals
|
||||
// case *ast.CompositeLit:
|
||||
// switch literal := boundItem.Type.(type) {
|
||||
|
||||
// app.Bind( myStruct{} )
|
||||
case *ast.Ident:
|
||||
structName := literal.Name
|
||||
boundStruct := newStructReference(pkg.Name, structName)
|
||||
boundStructs = append(boundStructs, boundStruct)
|
||||
// // app.Bind( myStruct{} )
|
||||
// case *ast.Ident:
|
||||
// structName := literal.Name
|
||||
// boundStructReference := newStructReference(p.GoPackage, structName)
|
||||
// p.addBoundStructReference(boundStructReference)
|
||||
|
||||
// app.Bind( mypackage.myStruct{} )
|
||||
case *ast.SelectorExpr:
|
||||
var structName = ""
|
||||
var packageName = ""
|
||||
switch x := literal.X.(type) {
|
||||
case *ast.Ident:
|
||||
packageName = x.Name
|
||||
default:
|
||||
// TODO: Save these warnings
|
||||
// println("Identifier in binding not supported:")
|
||||
return true
|
||||
}
|
||||
structName = literal.Sel.Name
|
||||
// // app.Bind( mypackage.myStruct{} )
|
||||
// case *ast.SelectorExpr:
|
||||
// var structName = ""
|
||||
// var packageName = ""
|
||||
// switch x := literal.X.(type) {
|
||||
// case *ast.Ident:
|
||||
// packageName = x.Name
|
||||
// default:
|
||||
// // TODO: Save these warnings
|
||||
// // println("Identifier in binding not supported:")
|
||||
// return true
|
||||
// }
|
||||
// structName = literal.Sel.Name
|
||||
|
||||
referencedPackage := p.getImportByName(pkg, packageName)
|
||||
boundStruct := newStructReference(referencedPackage.Name, structName)
|
||||
boundStructs = append(boundStructs, boundStruct)
|
||||
}
|
||||
// referencedPackage := p.getImportByName(pkg, packageName)
|
||||
// boundStructReference := newStructReference(referencedPackage, structName)
|
||||
// p.addBoundStructReference(boundStructReference)
|
||||
// }
|
||||
|
||||
default:
|
||||
// TODO: Save these warnings
|
||||
@@ -150,6 +151,11 @@ func (p *Parser) findBoundStructsInPackage(pkg *packages.Package, applicationVar
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
if parseError != nil {
|
||||
return parseError
|
||||
}
|
||||
}
|
||||
return boundStructs
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package parser
|
||||
|
||||
func (p *Parser) getFunctionReturnType(packageName string, functionName string) *Struct {
|
||||
return nil
|
||||
}
|
||||
131
v2/pkg/parser/generate.go
Normal file
131
v2/pkg/parser/generate.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
)
|
||||
|
||||
// GenerateWailsFrontendPackage will generate a Javascript/Typescript
|
||||
// package in `<project>/frontend/wails` that defines which methods
|
||||
// and structs are bound to your frontend
|
||||
func GenerateWailsFrontendPackage() error {
|
||||
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := NewParser()
|
||||
|
||||
err = p.ParseProject(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.generateModule()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) generateModule() error {
|
||||
|
||||
moduleDir, err := createBackendJSDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pkg := range p.packages {
|
||||
|
||||
// Calculate directory
|
||||
dir := filepath.Join(moduleDir, pkg.gopackage.Name)
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
fs.Mkdir(dir)
|
||||
|
||||
err := generatePackage(pkg, dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBackendJSDirectory() (string, error) {
|
||||
|
||||
// Calculate the package directory
|
||||
// Note this is *always* called from the project directory
|
||||
// so using paths relative to CWD is fine
|
||||
dir, err := fs.RelativeToCwd("./frontend/wails")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error creating wails js directory")
|
||||
}
|
||||
|
||||
// Remove directory if it exists - REGENERATION!
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error removing module directory")
|
||||
}
|
||||
|
||||
// Make the directory
|
||||
err = fs.Mkdir(dir)
|
||||
|
||||
return dir, err
|
||||
}
|
||||
|
||||
func generatePackage(pkg *Package, moduledir string) error {
|
||||
|
||||
// Get path to local file
|
||||
typescriptTemplateFile := fs.RelativePath("./package.d.template")
|
||||
|
||||
// Load typescript template
|
||||
typescriptTemplateData := fs.MustLoadString(typescriptTemplateFile)
|
||||
typescriptTemplate, err := template.New("typescript").Parse(typescriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
}
|
||||
|
||||
// Execute javascript template
|
||||
var buffer bytes.Buffer
|
||||
err = typescriptTemplate.Execute(&buffer, pkg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save typescript file
|
||||
err = ioutil.WriteFile(filepath.Join(moduledir, "index.d.ts"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
// Get path to local file
|
||||
javascriptTemplateFile := fs.RelativePath("./package.template")
|
||||
|
||||
// Load javascript template
|
||||
javascriptTemplateData := fs.MustLoadString(javascriptTemplateFile)
|
||||
javascriptTemplate, err := template.New("javascript").Parse(javascriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
}
|
||||
|
||||
// Reset the buffer
|
||||
buffer.Reset()
|
||||
|
||||
err = javascriptTemplate.Execute(&buffer, pkg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save javascript file
|
||||
err = ioutil.WriteFile(filepath.Join(moduledir, "index.js"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strings"
|
||||
)
|
||||
@@ -14,7 +15,8 @@ type Method struct {
|
||||
}
|
||||
|
||||
func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
for _, fileAst := range boundStruct.Package.Syntax {
|
||||
|
||||
for _, fileAst := range boundStruct.Package.gopackage.Syntax {
|
||||
|
||||
// Track errors
|
||||
var parseError error
|
||||
@@ -43,7 +45,7 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
}
|
||||
|
||||
// We want to ignore Internal functions
|
||||
if p.internalMethods.Contains(funcDecl.Name.Name) {
|
||||
if funcDecl.Name.Name == "WailsInit" || funcDecl.Name.Name == "WailsShutdown" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -55,13 +57,13 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
// Create our struct
|
||||
structMethod := &Method{
|
||||
Name: funcDecl.Name.Name,
|
||||
Comments: p.parseComments(funcDecl.Doc),
|
||||
Comments: parseComments(funcDecl.Doc),
|
||||
}
|
||||
|
||||
// Save the input parameters
|
||||
if funcDecl.Type.Params != nil {
|
||||
for _, inputField := range funcDecl.Type.Params.List {
|
||||
fields, err := p.parseField(inputField, boundStruct.Package.Name)
|
||||
fields, err := p.parseField(fileAst, inputField, boundStruct.Package)
|
||||
if err != nil {
|
||||
parseError = err
|
||||
return false
|
||||
@@ -74,7 +76,7 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
// Save the output parameters
|
||||
if funcDecl.Type.Results != nil {
|
||||
for _, outputField := range funcDecl.Type.Results.List {
|
||||
fields, err := p.parseField(outputField, boundStruct.Package.Name)
|
||||
fields, err := p.parseField(fileAst, outputField, boundStruct.Package)
|
||||
if err != nil {
|
||||
parseError = err
|
||||
return false
|
||||
@@ -104,3 +106,44 @@ func (p *Parser) parseStructMethods(boundStruct *Struct) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Typescript
|
||||
func (m *Method) InputsAsTSText(pkgName string) string {
|
||||
var inputs []string
|
||||
|
||||
for _, input := range m.Inputs {
|
||||
inputText := fmt.Sprintf("%s: %s", input.Name, goTypeToTS(input, pkgName))
|
||||
inputs = append(inputs, inputText)
|
||||
}
|
||||
|
||||
return strings.Join(inputs, ", ")
|
||||
}
|
||||
|
||||
// OutputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *Method) OutputsAsTSText(pkgName string) string {
|
||||
|
||||
if len(m.Returns) == 0 {
|
||||
return "void"
|
||||
}
|
||||
|
||||
var result []string
|
||||
|
||||
for _, output := range m.Returns {
|
||||
result = append(result, goTypeToTS(output, pkgName))
|
||||
}
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
// InputsAsJSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *Method) InputsAsJSText() string {
|
||||
var inputs []string
|
||||
|
||||
for _, input := range m.Inputs {
|
||||
inputs = append(inputs, input.Name)
|
||||
}
|
||||
|
||||
return strings.Join(inputs, ", ")
|
||||
}
|
||||
|
||||
@@ -6,18 +6,17 @@
|
||||
/// <reference types="../{{.}}" />{{end}}{{- end}}
|
||||
|
||||
declare module {{.Name}} { {{range .Structs}}
|
||||
{{- $usedAsData := $.StructIsUsedAsData .Name }}
|
||||
{{- if or .IsBound $usedAsData}}
|
||||
{{- if or .IsBound .IsUsedAsData}}
|
||||
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{- end}}
|
||||
interface {{.Name}} { {{ if $usedAsData }}
|
||||
interface {{.Name}} { {{ if .IsUsedAsData }}
|
||||
{{- range .Fields}}{{if .Comments }}
|
||||
{{range .Comments}}//{{ . }}{{end}}{{- end}}
|
||||
{{.Name}}: {{.TypeAsTSType}}; {{- end}} {{ end }}
|
||||
{{.Name}}: {{.TypeAsTSType $.Name}}; {{- end}} {{ end }}
|
||||
{{- if .IsBound }}
|
||||
{{- range .Methods}}
|
||||
{{- range .Comments}}
|
||||
// {{ . }}{{- end}}
|
||||
{{.Name}}({{.InputsAsTSText}}): Promise<{{.OutputsAsTSText}}>;
|
||||
{{.Name}}({{.InputsAsTSText $.Name}}): Promise<{{.OutputsAsTSText $.Name}}>;
|
||||
{{- end}}{{end}}
|
||||
}{{- end}}
|
||||
{{end}}
|
||||
122
v2/pkg/parser/package.go
Normal file
122
v2/pkg/parser/package.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Package is a wrapper around the go parsed package
|
||||
type Package struct {
|
||||
|
||||
// A unique Name for this package.
|
||||
// This is calculated and may not be the same as the one
|
||||
// defined in Go - but that's ok!
|
||||
Name string
|
||||
|
||||
// the package we are wrapping
|
||||
gopackage *packages.Package
|
||||
|
||||
// a list of struct names that are bound in this package
|
||||
boundStructs slicer.StringSlicer
|
||||
|
||||
// Structs used in this package
|
||||
parsedStructs map[string]*Struct
|
||||
|
||||
// A list of external packages we reference from this package
|
||||
externalReferences slicer.InterfaceSlicer
|
||||
}
|
||||
|
||||
func newPackage(pkg *packages.Package) *Package {
|
||||
return &Package{
|
||||
gopackage: pkg,
|
||||
parsedStructs: make(map[string]*Struct),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Package) getWailsImportName(file *ast.File) string {
|
||||
// Scan the imports for the wails v2 import
|
||||
for _, details := range file.Imports {
|
||||
if details.Path.Value == `"github.com/wailsapp/wails/v2"` {
|
||||
if details.Name != nil {
|
||||
return details.Name.Name
|
||||
}
|
||||
|
||||
// Get the import name from the package
|
||||
imp := p.getImportByPath("github.com/wailsapp/wails/v2")
|
||||
if imp != nil {
|
||||
return imp.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p *Package) getImportByName(importName string, file *ast.File) *packages.Package {
|
||||
|
||||
// Check if the file has aliased the import
|
||||
for _, imp := range file.Imports {
|
||||
if imp.Name != nil {
|
||||
if imp.Name.Name == importName {
|
||||
// Yes it has. Get the import by path
|
||||
return p.getImportByPath(imp.Path.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to find which package import has this name
|
||||
for _, imp := range p.gopackage.Imports {
|
||||
if imp.Name == importName {
|
||||
return imp
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like this package is outside the project...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Package) getImportByPath(packagePath string) *packages.Package {
|
||||
packagePath = strings.Trim(packagePath, "\"")
|
||||
return p.gopackage.Imports[packagePath]
|
||||
}
|
||||
|
||||
func (p *Package) getStruct(structName string) *Struct {
|
||||
return p.parsedStructs[structName]
|
||||
}
|
||||
|
||||
func (p *Package) addStruct(strct *Struct) {
|
||||
p.parsedStructs[strct.Name] = strct
|
||||
}
|
||||
|
||||
// DeclarationReferences returns a list of external packages
|
||||
// we reference from this package
|
||||
func (p *Package) DeclarationReferences() []string {
|
||||
|
||||
var referenceNames slicer.StringSlicer
|
||||
|
||||
// Generics can't come soon enough!
|
||||
p.externalReferences.Each(func(p interface{}) {
|
||||
referenceNames.Add(p.(*Package).Name)
|
||||
})
|
||||
|
||||
return referenceNames.AsSlice()
|
||||
}
|
||||
|
||||
// addExternalReference saves the given package as an external reference
|
||||
func (p *Package) addExternalReference(pkg *Package) {
|
||||
p.externalReferences.AddUnique(pkg)
|
||||
}
|
||||
|
||||
// Structs returns the structs that we want to generate
|
||||
func (p *Package) Structs() []*Struct {
|
||||
|
||||
var result []*Struct
|
||||
|
||||
for _, elem := range p.parsedStructs {
|
||||
result = append(result, elem)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export const {{.Name}} = {
|
||||
* @function {{.Name}}
|
||||
{{range .Inputs}} * @param {{"{"}}{{.JSType}}{{"}"}} {{.Name}}
|
||||
{{end}} *
|
||||
* @returns {Promise<{{.OutputsAsTSText}}>}
|
||||
* @returns {Promise<{{.OutputsAsTSText $.Name}}>}
|
||||
*/
|
||||
{{.Name}}: function({{.InputsAsJSText}}) {
|
||||
return window.backend.{{$.Name}}.{{$struct.Name}}.{{.Name}}({{.InputsAsJSText}});
|
||||
75
v2/pkg/parser/parseBoundStructs.go
Normal file
75
v2/pkg/parser/parseBoundStructs.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package parser
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func (p *Parser) parseBoundStructs(pkg *Package) error {
|
||||
|
||||
// Loop over the bound structs
|
||||
for _, structName := range pkg.boundStructs.AsSlice() {
|
||||
strct, err := p.parseStruct(pkg, structName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
strct.IsBound = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseStruct will attempt to parse the given struct using
|
||||
// the package it references
|
||||
func (p *Parser) parseStruct(pkg *Package, structName string) (*Struct, error) {
|
||||
|
||||
// Check the parser cache for this struct
|
||||
result := pkg.getStruct(structName)
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Iterate through the whole package looking for the bound structs
|
||||
for _, fileAst := range pkg.gopackage.Syntax {
|
||||
|
||||
// Track errors
|
||||
var parseError error
|
||||
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
if genDecl, ok := n.(*ast.GenDecl); ok {
|
||||
for _, spec := range genDecl.Specs {
|
||||
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
||||
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||
structDefinitionName := typeSpec.Name.Name
|
||||
if structDefinitionName == structName {
|
||||
|
||||
// Create the new struct
|
||||
result = &Struct{Name: structName, Package: pkg}
|
||||
|
||||
// Save comments
|
||||
result.Comments = parseComments(genDecl.Doc)
|
||||
|
||||
parseError = p.parseStructMethods(result)
|
||||
if parseError != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the struct fields
|
||||
parseError = p.parseStructFields(fileAst, structType, result)
|
||||
|
||||
// Save this struct
|
||||
pkg.addStruct(result)
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// If we got an error, return it
|
||||
if parseError != nil {
|
||||
return nil, parseError
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
)
|
||||
|
||||
// getCachedStruct attempts to get an already parsed struct from the
|
||||
// struct cache
|
||||
func (p *Parser) getCachedStruct(packageName string, structName string) *Struct {
|
||||
fqn := packageName + "." + structName
|
||||
return p.parsedStructs[fqn]
|
||||
}
|
||||
|
||||
// ParseStruct will attempt to parse the given struct using
|
||||
// the package it references
|
||||
func (p *Parser) ParseStruct(packageName string, structName string) (*Struct, error) {
|
||||
|
||||
// Check the cache
|
||||
result := p.getCachedStruct(packageName, structName)
|
||||
if result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Find the package
|
||||
pkg := p.getPackageByName(packageName)
|
||||
if pkg == nil {
|
||||
// TODO: Find package via imports?
|
||||
println("Cannot find package", packageName)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Iterate through the whole package looking for the bound structs
|
||||
for _, fileAst := range pkg.Syntax {
|
||||
|
||||
// Track errors
|
||||
var parseError error
|
||||
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
if genDecl, ok := n.(*ast.GenDecl); ok {
|
||||
for _, spec := range genDecl.Specs {
|
||||
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
||||
if structType, ok := typeSpec.Type.(*ast.StructType); ok {
|
||||
structDefinitionName := typeSpec.Name.Name
|
||||
if structDefinitionName == structName {
|
||||
|
||||
// Create the new struct
|
||||
result = p.newStruct(pkg, structDefinitionName)
|
||||
|
||||
// Save comments
|
||||
result.Comments = p.parseComments(genDecl.Doc)
|
||||
|
||||
parseError = p.parseStructMethods(result)
|
||||
if parseError != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the struct fields
|
||||
parseError = p.parseStructFields(structType, result)
|
||||
|
||||
// Cache this struct
|
||||
key := result.FullyQualifiedName()
|
||||
p.parsedStructs[key] = result
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// If we got an error, return it
|
||||
if parseError != nil {
|
||||
return nil, parseError
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseStructFields(structType *ast.StructType, boundStruct *Struct) error {
|
||||
|
||||
// Parse the fields
|
||||
for _, field := range structType.Fields.List {
|
||||
fields, err := p.parseField(field, boundStruct.Package.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
boundStruct.Fields = append(boundStruct.Fields, fields...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
17
v2/pkg/parser/parseStructFields.go
Normal file
17
v2/pkg/parser/parseStructFields.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package parser
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func (p *Parser) parseStructFields(fileAst *ast.File, structType *ast.StructType, boundStruct *Struct) error {
|
||||
|
||||
// Parse the fields
|
||||
for _, field := range structType.Fields.List {
|
||||
fields, err := p.parseField(fileAst, field, boundStruct.Package)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
boundStruct.Fields = append(boundStruct.Fields, fields...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,34 +3,27 @@ package parser
|
||||
import (
|
||||
"go/token"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Parser is the Wails project parser
|
||||
type Parser struct {
|
||||
|
||||
// Placeholders for Go's parser
|
||||
goPackages []*packages.Package
|
||||
fileSet *token.FileSet
|
||||
internalMethods *slicer.StringSlicer
|
||||
fileSet *token.FileSet
|
||||
|
||||
// This is a map of structs that have been parsed
|
||||
// The key is <package>.<structname>
|
||||
parsedStructs map[string]*Struct
|
||||
|
||||
// The list of struct names that are bound
|
||||
BoundStructReferences []*StructReference
|
||||
|
||||
// The list of structs that are bound
|
||||
BoundStructs []*Struct
|
||||
// The packages we parse
|
||||
// The map key is the package ID
|
||||
packages map[string]*Package
|
||||
}
|
||||
|
||||
// NewParser creates a new Wails project parser
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
fileSet: token.NewFileSet(),
|
||||
internalMethods: slicer.String([]string{"WailsInit", "WailsShutdown"}),
|
||||
parsedStructs: make(map[string]*Struct),
|
||||
fileSet: token.NewFileSet(),
|
||||
packages: make(map[string]*Package),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,17 +37,29 @@ func (p *Parser) ParseProject(dir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.findBoundStructs()
|
||||
if err != nil {
|
||||
return err
|
||||
// Find all the bound structs
|
||||
for _, pkg := range p.packages {
|
||||
err = p.findBoundStructs(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = p.parseBoundStructs()
|
||||
if err != nil {
|
||||
return err
|
||||
// Parse the structs
|
||||
for _, pkg := range p.packages {
|
||||
err = p.parseBoundStructs(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
// Resolve package names
|
||||
// We do this because some packages may have the same name
|
||||
p.resolvePackageNames()
|
||||
|
||||
spew.Dump(p.packages)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) loadPackages(projectPath string) error {
|
||||
@@ -63,7 +68,8 @@ func (p *Parser) loadPackages(projectPath string) error {
|
||||
packages.NeedSyntax |
|
||||
packages.NeedTypes |
|
||||
packages.NeedImports |
|
||||
packages.NeedTypesInfo
|
||||
packages.NeedTypesInfo |
|
||||
packages.NeedModule
|
||||
|
||||
cfg := &packages.Config{Fset: p.fileSet, Mode: mode, Dir: projectPath}
|
||||
pkgs, err := packages.Load(cfg, "./...")
|
||||
@@ -86,77 +92,14 @@ func (p *Parser) loadPackages(projectPath string) error {
|
||||
return parseError
|
||||
}
|
||||
|
||||
p.goPackages = pkgs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) getPackageByName(packageName string) *packages.Package {
|
||||
for _, pkg := range p.goPackages {
|
||||
if pkg.Name == packageName {
|
||||
return pkg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) getWailsImportName(pkg *packages.Package) (string, bool) {
|
||||
// Scan the imports for the wails v2 import
|
||||
for key, details := range pkg.Imports {
|
||||
if key == "github.com/wailsapp/wails/v2" {
|
||||
return details.Name, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// findBoundStructs will search through the Wails project looking
|
||||
// for which structs have been bound using the `Bind()` method
|
||||
func (p *Parser) findBoundStructs() error {
|
||||
|
||||
// Try each of the packages to find the Bind() calls
|
||||
for _, pkg := range p.goPackages {
|
||||
|
||||
// Does this package import Wails?
|
||||
wailsImportName, imported := p.getWailsImportName(pkg)
|
||||
if !imported {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do we create an app using CreateApp?
|
||||
appVariableName, created := p.getApplicationVariableName(pkg, wailsImportName)
|
||||
if !created {
|
||||
continue
|
||||
}
|
||||
|
||||
boundStructReferences := p.findBoundStructsInPackage(pkg, appVariableName)
|
||||
p.BoundStructReferences = append(p.BoundStructReferences, boundStructReferences...)
|
||||
// Create a map of packages
|
||||
for _, pkg := range pkgs {
|
||||
p.packages[pkg.ID] = newPackage(pkg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseBoundStructs() error {
|
||||
|
||||
// Iterate the structs
|
||||
for _, boundStructReference := range p.BoundStructReferences {
|
||||
// Parse the struct
|
||||
boundStruct, err := p.ParseStruct(boundStructReference.Package, boundStructReference.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.BoundStructs = append(p.BoundStructs, boundStruct)
|
||||
}
|
||||
|
||||
// Resolve the references between the structs
|
||||
// This is when a field of one struct is a struct type
|
||||
for _, boundStruct := range p.BoundStructs {
|
||||
err := p.resolveStructReferences(boundStruct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (p *Parser) getPackageByID(id string) *Package {
|
||||
return p.packages[id]
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/matryer/is"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
// Local project dir
|
||||
projectDir := fs.RelativePath("./testproject")
|
||||
|
||||
p := NewParser()
|
||||
|
||||
// Check parsing worked
|
||||
err := p.ParseProject(projectDir)
|
||||
is.NoErr(err)
|
||||
|
||||
// Expected structs
|
||||
expectedBoundStructs := slicer.String()
|
||||
expectedBoundStructs.Add("main.Basic", "mypackage.Manager")
|
||||
|
||||
// We expect these to be the same length
|
||||
is.Equal(expectedBoundStructs.Length(), len(p.BoundStructs))
|
||||
|
||||
// Check bound structs
|
||||
for _, boundStruct := range p.BoundStructs {
|
||||
|
||||
// Check the names are correct
|
||||
fqn := boundStruct.FullyQualifiedName()
|
||||
is.True(expectedBoundStructs.Contains(fqn))
|
||||
|
||||
// Check that the structs have comments
|
||||
is.True(len(boundStruct.Comments) > 0)
|
||||
|
||||
// Check that the structs have methods
|
||||
is.True(len(boundStruct.Methods) > 0)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
35
v2/pkg/parser/resolvePackageReferences.go
Normal file
35
v2/pkg/parser/resolvePackageReferences.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
)
|
||||
|
||||
// resolvePackageNames will deterine the names for the packages, allowing
|
||||
// us to create a flat structure for the imports in the frontend module
|
||||
func (p *Parser) resolvePackageNames() {
|
||||
|
||||
// A cache for the names
|
||||
var packageNameCache slicer.StringSlicer
|
||||
|
||||
// Process each package
|
||||
for _, pkg := range p.packages {
|
||||
pkgName := pkg.gopackage.Name
|
||||
|
||||
// Check for collision
|
||||
if packageNameCache.Contains(pkgName) {
|
||||
// https://www.youtube.com/watch?v=otNNGROI0Cs !!!!!
|
||||
|
||||
// We start at 2 because having both "pkg" and "pkg1" is 🙄
|
||||
count := 2
|
||||
for ok := true; ok; ok = packageNameCache.Contains(pkgName) {
|
||||
pkgName = fmt.Sprintf("%s%d", pkg.gopackage.Name, count)
|
||||
}
|
||||
}
|
||||
|
||||
// Save the name!
|
||||
packageNameCache.Add(pkgName)
|
||||
pkg.Name = pkgName
|
||||
}
|
||||
}
|
||||
@@ -4,37 +4,36 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Struct represents a struct that is used by the frontend
|
||||
// in a Wails project
|
||||
type Struct struct {
|
||||
Package *packages.Package
|
||||
Name string
|
||||
|
||||
// The name of the struct
|
||||
Name string
|
||||
|
||||
// The package this was declared in
|
||||
Package *Package
|
||||
|
||||
// Comments for the struct
|
||||
Comments []string
|
||||
Fields []*Field
|
||||
Methods []*Method
|
||||
|
||||
// This is true when this struct is used as a datatype
|
||||
UsedAsData bool
|
||||
// The fields used in this struct
|
||||
Fields []*Field
|
||||
|
||||
// The methods available to the front end
|
||||
Methods []*Method
|
||||
|
||||
// Indicates if this struct is bound to the app
|
||||
IsBound bool
|
||||
|
||||
// Indicates if this struct is used as data
|
||||
IsUsedAsData bool
|
||||
}
|
||||
|
||||
// newStruct creates a new struct and stores in the cache
|
||||
func (p *Parser) newStruct(pkg *packages.Package, name string) *Struct {
|
||||
|
||||
result := &Struct{
|
||||
Package: pkg,
|
||||
Name: name,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FullyQualifiedName returns the fully qualified name of this struct
|
||||
func (s *Struct) FullyQualifiedName() string {
|
||||
return s.Package.Name + "." + s.Name
|
||||
}
|
||||
|
||||
func (p *Parser) parseStructNameFromStarExpr(starExpr *ast.StarExpr) (string, string, error) {
|
||||
func parseStructNameFromStarExpr(starExpr *ast.StarExpr) (string, string, error) {
|
||||
pkg := ""
|
||||
name := ""
|
||||
// Determine the FQN
|
||||
@@ -44,66 +43,26 @@ func (p *Parser) parseStructNameFromStarExpr(starExpr *ast.StarExpr) (string, st
|
||||
case *ast.Ident:
|
||||
pkg = i.Name
|
||||
default:
|
||||
return "", "", fmt.Errorf("Unsupported Selector expression: %+v", i)
|
||||
// TODO: Store warnings?
|
||||
return "", "", errors.WithStack(fmt.Errorf("Unknown type in selector for *ast.SelectorExpr: ", i))
|
||||
}
|
||||
|
||||
name = x.Sel.Name
|
||||
|
||||
// TODO: IS this used?
|
||||
case *ast.StarExpr:
|
||||
switch s := x.X.(type) {
|
||||
case *ast.Ident:
|
||||
name = s.Name
|
||||
default:
|
||||
return "", "", fmt.Errorf("Unsupported Star expression: %+v", s)
|
||||
// TODO: Store warnings?
|
||||
return "", "", errors.WithStack(fmt.Errorf("Unknown type in selector for *ast.StarExpr: ", s))
|
||||
}
|
||||
case *ast.Ident:
|
||||
name = x.Name
|
||||
default:
|
||||
return "", "", fmt.Errorf("Unsupported Star.X expression: %+v", x)
|
||||
// TODO: Store warnings?
|
||||
return "", "", errors.WithStack(fmt.Errorf("Unknown type in selector for *ast.StarExpr: ", starExpr))
|
||||
}
|
||||
return pkg, name, nil
|
||||
}
|
||||
|
||||
// StructReference defines a reference to a fully qualified struct
|
||||
type StructReference struct {
|
||||
Package string
|
||||
Name string
|
||||
}
|
||||
|
||||
func newStructReference(packageName string, structName string) *StructReference {
|
||||
return &StructReference{Package: packageName, Name: structName}
|
||||
}
|
||||
|
||||
// FullyQualifiedName returns a string representing the struct reference
|
||||
func (s *StructReference) FullyQualifiedName() string {
|
||||
return s.Package + "." + s.Name
|
||||
}
|
||||
|
||||
func (p *Parser) resolveStructReferences(boundStruct *Struct) error {
|
||||
|
||||
var err error
|
||||
|
||||
// Resolve field references
|
||||
err = p.resolveFieldReferences(boundStruct.Fields)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if method fields need resolving
|
||||
for _, method := range boundStruct.Methods {
|
||||
|
||||
// Resolve method inputs
|
||||
err = p.resolveFieldReferences(method.Inputs)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve method outputs
|
||||
err = p.resolveFieldReferences(method.Returns)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"testproject/mypackage"
|
||||
|
||||
wails "github.com/wailsapp/wails/v2"
|
||||
)
|
||||
|
||||
@@ -43,17 +45,22 @@ func (b *Basic) WailsShutdown() {
|
||||
// Perform your teardown here
|
||||
}
|
||||
|
||||
// // NewPerson creates a new person
|
||||
// func (b *Basic) NewPerson(name string, age int) *mypackage.Person {
|
||||
// return &mypackage.Person{Name: name, Age: age}
|
||||
// }
|
||||
// NewPerson creates a new person
|
||||
func (b *Basic) NewPerson(name string, age int) *mypackage.Person {
|
||||
return &mypackage.Person{Name: name, Age: age}
|
||||
}
|
||||
|
||||
// Greet returns a greeting for the given name
|
||||
func (b *Basic) Greet(name string) string {
|
||||
return fmt.Sprintf("Hello %s!", name)
|
||||
}
|
||||
|
||||
// // RemovePerson Removes the given person
|
||||
// func (b *Basic) RemovePerson(p *mypackage.Person) {
|
||||
// // dummy
|
||||
// }
|
||||
// MultipleGreets returns greetings for the given name
|
||||
func (b *Basic) MultipleGreets(name string) []string {
|
||||
return []string{"hi", "hello", "croeso!"}
|
||||
}
|
||||
|
||||
// RemovePerson Removes the given person
|
||||
func (b *Basic) RemovePerson(p *mypackage.Person) {
|
||||
// dummy
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user