mirror of
https://github.com/taigrr/wails.git
synced 2026-04-16 03:40:49 -07:00
Rewritten module generator (TS Only)
This commit is contained in:
@@ -8,7 +8,7 @@ require (
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/leaanthony/clir v1.0.4
|
||||
github.com/leaanthony/gosod v0.0.4
|
||||
github.com/leaanthony/slicer v1.4.1
|
||||
github.com/leaanthony/slicer v1.5.0
|
||||
github.com/matryer/is v1.4.0
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
|
||||
@@ -46,6 +46,8 @@ github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQp
|
||||
github.com/leaanthony/gosod v0.0.4/go.mod h1:nGMCb1PJfXwBDbOAike78jEYlpqge+xUKFf0iBKjKxU=
|
||||
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
|
||||
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY=
|
||||
github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
@@ -88,6 +90,7 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wailsapp/wails v1.9.0 h1:KF6tQRoQIp/LMEM/kvecG5+HCP80h1WLl+g3WpkL1VA=
|
||||
github.com/wailsapp/wails v1.9.1 h1:ez/TK8YpU9lvOZ9nkgzUXsWu+xOPFVO57zTy0n5w3hc=
|
||||
github.com/xyproto/xpm v1.2.1 h1:trdvGjjWBsOOKzBBUPT6JvaIQM3acJEEYfbxN7M96wg=
|
||||
github.com/xyproto/xpm v1.2.1/go.mod h1:cMnesLsD0PBXLgjDfTDEaKr8XyTFsnP1QycSqRw7BiY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
||||
@@ -91,13 +91,11 @@ func Build(options *Options) (string, error) {
|
||||
builder.SetProjectData(projectData)
|
||||
|
||||
// Generate Frontend JS Package
|
||||
outputLogger.Print(" - Generating Backend JS Package")
|
||||
outputLogger.Println(" - Generating Backend JS Package")
|
||||
err = backendjs.GenerateBackendJSPackage()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outputLogger.Println("done.")
|
||||
|
||||
if !options.IgnoreFrontend {
|
||||
outputLogger.Println(" - Building Wails Frontend")
|
||||
err = builder.BuildFrontend(outputLogger)
|
||||
|
||||
62
v2/pkg/commands/build/internal/backendjs/assignments.go
Normal file
62
v2/pkg/commands/build/internal/backendjs/assignments.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package backendjs
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func (p *Parser) parseAssignment(assignStmt *ast.AssignStmt) {
|
||||
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
|
||||
p.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 {
|
||||
p.variablesThatWereAssignedByStructLiterals[i.Name] = t.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"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()
|
||||
@@ -19,17 +22,17 @@ func GenerateBackendJSPackage() error {
|
||||
|
||||
p := NewParser()
|
||||
|
||||
err = p.ParseProject(dir)
|
||||
err = p.parseProject(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.GenerateModule()
|
||||
err = p.generateModule()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) ParseProject(projectPath string) error {
|
||||
func (p *Parser) parseProject(projectPath string) error {
|
||||
mode := packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedSyntax |
|
||||
@@ -46,19 +49,24 @@ func (p *Parser) ParseProject(projectPath string) error {
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
return errors.Wrap(err, "Errors during parsing")
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
parsedPackage, err := p.ParsePackage(pkg, fset)
|
||||
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 {
|
||||
func (p *Parser) generateModule() error {
|
||||
|
||||
moduleDir, err := createBackendJSDirectory()
|
||||
if err != nil {
|
||||
|
||||
60
v2/pkg/commands/build/internal/backendjs/calls.go
Normal file
60
v2/pkg/commands/build/internal/backendjs/calls.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package backendjs
|
||||
|
||||
import "go/ast"
|
||||
|
||||
func (p *Parser) parseCallExpressions(x *ast.CallExpr) {
|
||||
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 {
|
||||
// We found a bind method using a function call
|
||||
// EG: app.Bind( newMyStruct() )
|
||||
p.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 {
|
||||
// We have found Bind( &MyStruct{} )
|
||||
p.structPointerLiteralsThatWereBound.Add(t.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
p.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 {
|
||||
p.variablesThatWereBound.Add(i.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseComments(comments *ast.CommentGroup) []string {
|
||||
func (p *Parser) parseComments(comments *ast.CommentGroup) []string {
|
||||
var result []string
|
||||
|
||||
if comments == nil {
|
||||
|
||||
76
v2/pkg/commands/build/internal/backendjs/conversion.go
Normal file
76
v2/pkg/commands/build/internal/backendjs/conversion.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package backendjs
|
||||
|
||||
// JSType represents a javascript type
|
||||
type JSType string
|
||||
|
||||
const (
|
||||
// JsString is a JS string
|
||||
JsString JSType = "string"
|
||||
// JsBoolean is a JS bool
|
||||
JsBoolean = "boolean"
|
||||
// JsInt is a JS number
|
||||
JsInt = "number"
|
||||
// JsFloat is a JS number
|
||||
JsFloat = "number"
|
||||
// JsArray is a JS array
|
||||
JsArray = "Array"
|
||||
// JsObject is a JS object
|
||||
JsObject = "Object"
|
||||
// JsUnsupported represents a type that cannot be converted
|
||||
JsUnsupported = "*"
|
||||
)
|
||||
|
||||
func goTypeToJS(input string) JSType {
|
||||
switch input {
|
||||
case "string":
|
||||
return JsString
|
||||
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
|
||||
return JsInt
|
||||
case "float32", "float64":
|
||||
return JsFloat
|
||||
case "bool":
|
||||
return JsBoolean
|
||||
// case reflect.Array, reflect.Slice:
|
||||
// return JsArray
|
||||
// case reflect.Ptr, reflect.Struct, reflect.Map, reflect.Interface:
|
||||
// return JsObject
|
||||
default:
|
||||
println("UNSUPPORTED: ", input)
|
||||
return JsUnsupported
|
||||
}
|
||||
}
|
||||
|
||||
func goTypeToTS(input *Field) string {
|
||||
var result string
|
||||
switch input.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 input.Struct.Package != "" {
|
||||
result = input.Struct.Package + "."
|
||||
}
|
||||
result += input.Struct.Name
|
||||
// case reflect.Array, reflect.Slice:
|
||||
// return string(JsArray)
|
||||
// case reflect.Ptr, reflect.Struct:
|
||||
// fqt := input.Type().String()
|
||||
// return strings.Split(fqt, ".")[1]
|
||||
// case reflect.Map, reflect.Interface:
|
||||
// return string(JsObject)
|
||||
default:
|
||||
println("UNSUPPORTED: ", input)
|
||||
return JsUnsupported
|
||||
}
|
||||
|
||||
// if input.IsArray {
|
||||
// result = "Array<" + result + ">"
|
||||
// }
|
||||
|
||||
return result
|
||||
}
|
||||
149
v2/pkg/commands/build/internal/backendjs/functions.go
Normal file
149
v2/pkg/commands/build/internal/backendjs/functions.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func (p *Parser) parseField(field *ast.Field, 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)
|
||||
pkg.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, 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, 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)
|
||||
p.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)
|
||||
p.functionsThatReturnStructs[functionName] = t.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
{{range .DeclarationReferences}}/// <reference types="../{{.}}" />{{end}}
|
||||
declare module {{.Name}} { {{range .Structs}}
|
||||
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
|
||||
interface {{.Name}} { {{range .Fields}}{{if .Comments }}
|
||||
interface {{.Name}} { {{ if $.StructIsUsedAsData .Name }}
|
||||
{{range .Fields}}{{if .Comments }}
|
||||
{{range .Comments}}//{{ . }}{{end}}{{end}}
|
||||
{{.Name}}: {{.TypeAsTSType}}; {{end}}
|
||||
{{.Name}}: {{.TypeAsTSType}}; {{end}} {{ end }}
|
||||
{{range .Methods}}
|
||||
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
|
||||
{{.Name}}({{.InputsAsTSText}}): Promise<{{.OutputsAsTSText}}>;
|
||||
{{end}}
|
||||
}{{end}}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
"golang.org/x/tools/go/packages"
|
||||
@@ -17,34 +18,75 @@ import (
|
||||
type Package struct {
|
||||
Name string
|
||||
Structs map[string]*Struct
|
||||
|
||||
// These are references to other packages
|
||||
packageReferences slicer.StringSlicer
|
||||
|
||||
// These are the structs declared in this package
|
||||
// that are used as data by either this or other packages
|
||||
structsUsedAsData slicer.StringSlicer
|
||||
}
|
||||
|
||||
func (p *Parser) ParsePackage(pkg *packages.Package, fset *token.FileSet) (*Package, error) {
|
||||
func (p *Parser) parsePackage(pkg *packages.Package, fset *token.FileSet) (*Package, error) {
|
||||
result := &Package{
|
||||
Name: pkg.Name,
|
||||
Structs: make(map[string]*Struct),
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// spew.Dump(structType)
|
||||
structName := typeDecl.Name.Name
|
||||
// findInFields(structTy.Fields, n, pkg.TypesInfo, fset)
|
||||
structDef, err := p.ParseStruct(structType, structName)
|
||||
structDef, err := p.ParseStruct(structType, structName, result)
|
||||
if err != nil {
|
||||
parseError = err
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse comments
|
||||
structDef.Comments = parseComments(typeDecl.Doc)
|
||||
structDef.Comments = p.parseComments(typeDecl.Doc)
|
||||
|
||||
result.Structs[structName] = structDef
|
||||
}
|
||||
}
|
||||
|
||||
// Capture call expressions
|
||||
if callExpr, ok := n.(*ast.CallExpr); ok {
|
||||
p.parseCallExpressions(callExpr)
|
||||
}
|
||||
|
||||
// Parse Assignments
|
||||
if assignStmt, ok := n.(*ast.AssignStmt); ok {
|
||||
p.parseAssignment(assignStmt)
|
||||
}
|
||||
|
||||
// Parse Function declarations
|
||||
if funcDecl, ok := n.(*ast.FuncDecl); ok {
|
||||
p.parseFunctionDeclaration(funcDecl, result)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
if parseError != nil {
|
||||
@@ -80,3 +122,14 @@ func generatePackage(pkg *Package, moduledir string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeclarationReferences returns the typescript declaration references for the package
|
||||
func (p *Package) DeclarationReferences() []string {
|
||||
return p.packageReferences.AsSlice()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,68 @@
|
||||
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
|
||||
|
||||
// A list of methods that returns structs to the Bind method
|
||||
// EG: app.Bind( newMyStruct() )
|
||||
structMethodsThatWereBound slicer.StringSlicer
|
||||
|
||||
// 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 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
|
||||
|
||||
// Internal methods (WailsInit/WailsShutdown)
|
||||
internalMethods *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
|
||||
}
|
||||
|
||||
// NewParser creates a new Wails Project parser
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
Packages: make(map[string]*Package),
|
||||
Packages: make(map[string]*Package),
|
||||
variablesThatWereAssignedByFunctions: make(map[string]string),
|
||||
variablesThatWereAssignedByStructLiterals: make(map[string]string),
|
||||
functionsThatReturnStructPointers: make(map[string]string),
|
||||
functionsThatReturnStructs: make(map[string]string),
|
||||
internalMethods: slicer.String([]string{"WailsInit", "WailsShutdown"}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) resolve() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
@@ -12,8 +14,16 @@ 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
|
||||
}
|
||||
|
||||
// StructName is used to define a fully qualified struct name
|
||||
// EG: mypackage.Person
|
||||
type StructName struct {
|
||||
Name string
|
||||
Package string
|
||||
@@ -51,16 +61,60 @@ func (f *Field) TypeAsTSType() string {
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Parser) ParseStruct(structType *ast.StructType, name string) (*Struct, error) {
|
||||
result := &Struct{Name: name}
|
||||
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.Fields = append(result.Fields, p.ParseField(field, pkg)...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) ParseField(field *ast.Field) []*Field {
|
||||
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, pkg *Package) []*Field {
|
||||
var result []*Field
|
||||
|
||||
var fieldType string
|
||||
@@ -70,44 +124,24 @@ func (p *Parser) ParseField(field *ast.Field) []*Field {
|
||||
case *ast.Ident:
|
||||
fieldType = t.Name
|
||||
case *ast.StarExpr:
|
||||
pkg := ""
|
||||
name := ""
|
||||
// Determine the FQN
|
||||
switch x := t.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)
|
||||
}
|
||||
fieldType = "struct"
|
||||
structName = &StructName{
|
||||
Name: name,
|
||||
Package: pkg,
|
||||
}
|
||||
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 {
|
||||
pkg.packageReferences.AddUnique(structName.Package)
|
||||
referencedPackage.structsUsedAsData.AddUnique(structName.Name)
|
||||
}
|
||||
|
||||
}
|
||||
default:
|
||||
FieldNotSupported(t)
|
||||
fieldNotSupported(t)
|
||||
}
|
||||
|
||||
// Loop over names
|
||||
@@ -115,7 +149,7 @@ func (p *Parser) ParseField(field *ast.Field) []*Field {
|
||||
|
||||
// Create a field per name
|
||||
thisField := &Field{
|
||||
Comments: parseComments(field.Doc),
|
||||
Comments: p.parseComments(field.Doc),
|
||||
}
|
||||
thisField.Name = name.Name
|
||||
thisField.Type = fieldType
|
||||
@@ -128,8 +162,45 @@ func (p *Parser) ParseField(field *ast.Field) []*Field {
|
||||
return result
|
||||
}
|
||||
|
||||
func FieldNotSupported(t interface{}) {
|
||||
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, ", ")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user