mirror of
https://github.com/taigrr/wails.git
synced 2026-04-13 18:38:11 -07:00
Reimagined parser
This commit is contained in:
@@ -1,45 +1,103 @@
|
||||
// Package backendjs deals with generating the `backend` Javascript module
|
||||
// used by the frontend to interact with Go
|
||||
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 attempt to create the backend javascript package
|
||||
// used by the frontend to access methods and structs
|
||||
func GenerateBackendJSPackage() error {
|
||||
|
||||
// Create directory
|
||||
err := createBackendJSDirectory()
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating backend directory:")
|
||||
return err
|
||||
}
|
||||
packages, err := parseProject(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Generate Packages
|
||||
err = generatePackages()
|
||||
err = generateModule(packages)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func parseProject(projectPath string) ([]*Package, 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, "Error generating method wrappers:")
|
||||
return nil, errors.Wrap(err, "Problem loading packages")
|
||||
}
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
return nil, errors.Wrap(err, "Errors during parsing")
|
||||
}
|
||||
|
||||
var result []*Package
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
parsedPackage, err := parsePackage(pkg, fset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, parsedPackage)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func generateModule(pkgs []*Package) error {
|
||||
|
||||
moduleDir, err := createBackendJSDirectory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pkg := range pkgs {
|
||||
|
||||
// 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() error {
|
||||
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")
|
||||
return "", errors.Wrap(err, "Error creating backend js directory")
|
||||
}
|
||||
|
||||
// Only create the directory if it doesn't exit
|
||||
if !fs.DirExists(dir) {
|
||||
return fs.Mkdir(dir)
|
||||
// Remove directory if it exists - REGENERATION!
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Error removing module directory")
|
||||
}
|
||||
|
||||
return nil
|
||||
// Make the directory
|
||||
err = fs.Mkdir(dir)
|
||||
|
||||
return dir, err
|
||||
}
|
||||
|
||||
21
v2/pkg/commands/build/internal/backendjs/comments.go
Normal file
21
v2/pkg/commands/build/internal/backendjs/comments.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func 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,71 +0,0 @@
|
||||
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 *ParsedParameter) 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 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
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// index.js
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
{{range .}}
|
||||
export { default as {{.Name}} } from './{{.Name}}';{{end}}
|
||||
@@ -1,9 +1,12 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
declare module {{.Struct.Name}} {
|
||||
{{range .Struct.Methods}}
|
||||
declare module {{.Name}} {
|
||||
{{range .Structs}}
|
||||
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
|
||||
function {{.Name}}({{.InputsAsTSText}}): Promise<{{.OutputsAsTSText}}>;
|
||||
interface {{.Name}} { {{range .Fields}}{{if .Comments }}
|
||||
{{range .Comments}}//{{ . }}{{end}}{{end}}
|
||||
{{.Name}}: {{.TypeAsTSType}}; {{end}}
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
82
v2/pkg/commands/build/internal/backendjs/package.go
Normal file
82
v2/pkg/commands/build/internal/backendjs/package.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
func parsePackage(pkg *packages.Package, fset *token.FileSet) (*Package, error) {
|
||||
result := &Package{
|
||||
Name: pkg.Name,
|
||||
Structs: make(map[string]*Struct),
|
||||
}
|
||||
for _, fileAst := range pkg.Syntax {
|
||||
var parseError error
|
||||
ast.Inspect(fileAst, func(n ast.Node) bool {
|
||||
if typeDecl, ok := n.(*ast.TypeSpec); ok {
|
||||
if structType, ok := typeDecl.Type.(*ast.StructType); ok {
|
||||
|
||||
// spew.Dump(structType)
|
||||
structName := typeDecl.Name.Name
|
||||
// findInFields(structTy.Fields, n, pkg.TypesInfo, fset)
|
||||
structDef, err := parseStruct(structType, structName)
|
||||
if err != nil {
|
||||
parseError = err
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse comments
|
||||
structDef.Comments = parseComments(typeDecl.Doc)
|
||||
|
||||
result.Structs[structName] = structDef
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
{{range .Struct.Methods }}
|
||||
/**{{if .Comments }}
|
||||
{{range .Comments}} * {{ . }}{{end}}
|
||||
*{{end}}
|
||||
* @function {{.Name}}
|
||||
{{range .Inputs}} * @param {{"{"}}{{.JSType}}{{"}"}} {{.Name}}
|
||||
{{end}} *
|
||||
* @returns {Promise<{{.OutputsAsTSText}}>}
|
||||
*/
|
||||
export function {{.Name}}({{.InputsAsJSText}}) {
|
||||
return window.backend.{{$.PackageName}}.{{$.Struct.Name}}.{{.Name}}({{.InputsAsJSText}});
|
||||
}
|
||||
{{end}}
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/wailsapp/wails/v2/internal/fs"
|
||||
)
|
||||
|
||||
// Package defines a single package that contains bound structs
|
||||
type Package struct {
|
||||
Name string
|
||||
Comments []string
|
||||
Structs []*ParsedStruct
|
||||
}
|
||||
|
||||
func generatePackages() error {
|
||||
|
||||
packages, err := parsePackages()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error parsing struct packages:")
|
||||
}
|
||||
|
||||
err = generateJSFiles(packages)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating struct js file:")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePackages() ([]*Package, error) {
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := parseProject(cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// result = append(result, &Package{
|
||||
// Name: "mypackage",
|
||||
// Comments: []string{"mypackage is awesome"},
|
||||
// Methods: []*Method{
|
||||
// {
|
||||
// Name: "Naked",
|
||||
// Comments: []string{"Naked is a method that does nothing"},
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// result = append(result, &Package{
|
||||
// Name: "otherpackage",
|
||||
// Comments: []string{"otherpackage is awesome"},
|
||||
// Methods: []*Method{
|
||||
// {
|
||||
// Name: "OneInput",
|
||||
// Comments: []string{"OneInput does stuff"},
|
||||
// Inputs: []*Parameter{
|
||||
// {
|
||||
// Name: "name",
|
||||
// Value: String,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Name: "TwoInputs",
|
||||
// Inputs: []*Parameter{
|
||||
// {
|
||||
// Name: "name",
|
||||
// Value: String,
|
||||
// },
|
||||
// {
|
||||
// Name: "age",
|
||||
// Value: Uint8,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Name: "TwoInputsAndOutput",
|
||||
// Inputs: []*Parameter{
|
||||
// {
|
||||
// Name: "name",
|
||||
// Value: String,
|
||||
// },
|
||||
// {
|
||||
// Name: "age",
|
||||
// Value: Uint8,
|
||||
// },
|
||||
// },
|
||||
// Outputs: []*Parameter{
|
||||
// {
|
||||
// Name: "result",
|
||||
// Value: Bool,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// Name: "StructInput",
|
||||
// Comments: []string{"StructInput takes a person"},
|
||||
// Inputs: []*Parameter{
|
||||
// {
|
||||
// Name: "person",
|
||||
// Value: NewPerson("John Thomas", 46),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func generateJSFiles(packages []*Package) error {
|
||||
|
||||
err := generateIndexJS(packages)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating index.js file")
|
||||
}
|
||||
|
||||
err = generatePackageFiles(packages)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating packages")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateIndexJS(packages []*Package) error {
|
||||
|
||||
// Get path to local file
|
||||
templateFile := fs.RelativePath("./index.template")
|
||||
|
||||
// Load template
|
||||
javascriptTemplateData := fs.MustLoadString(templateFile)
|
||||
packagesTemplate, err := template.New("index").Parse(javascriptTemplateData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating template")
|
||||
}
|
||||
|
||||
// Execute template
|
||||
var buffer bytes.Buffer
|
||||
err = packagesTemplate.Execute(&buffer, packages)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Calculate target filename
|
||||
indexJS, err := fs.RelativeToCwd("./frontend/backend/index.js")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error calculating index js path")
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(indexJS, buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package index.js file")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePackageFiles(packages []*Package) error {
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
// Iterate over each package
|
||||
for _, thisPackage := range packages {
|
||||
err := generatePackage(thisPackage, typescriptTemplate, javascriptTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePackage(thisPackage *Package, typescriptTemplate *template.Template, javascriptTemplate *template.Template) error {
|
||||
|
||||
// Calculate target directory
|
||||
packageDir, err := fs.RelativeToCwd("./frontend/backend/" + thisPackage.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error calculating package path")
|
||||
}
|
||||
|
||||
// Make the dir but ignore if it already exists
|
||||
fs.Mkdir(packageDir)
|
||||
|
||||
type TemplateData struct {
|
||||
PackageName string
|
||||
Struct *ParsedStruct
|
||||
}
|
||||
|
||||
// Loop over structs
|
||||
for _, strct := range thisPackage.Structs {
|
||||
|
||||
var data = &TemplateData{
|
||||
PackageName: thisPackage.Name,
|
||||
Struct: strct,
|
||||
}
|
||||
|
||||
// Execute javascript template
|
||||
var buffer bytes.Buffer
|
||||
err = javascriptTemplate.Execute(&buffer, data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save javascript file
|
||||
err = ioutil.WriteFile(filepath.Join(packageDir, strct.Name+".js"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
|
||||
// Clear buffer
|
||||
buffer.Reset()
|
||||
|
||||
// Execute typescript template
|
||||
err = typescriptTemplate.Execute(&buffer, data)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error generating code")
|
||||
}
|
||||
|
||||
// Save typescript file
|
||||
err = ioutil.WriteFile(filepath.Join(packageDir, strct.Name+".d.ts"), buffer.Bytes(), 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error writing backend package file")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,501 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/leaanthony/slicer"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
wailsPkgVar string
|
||||
appVarName string
|
||||
|
||||
boundStructLiterals slicer.StringSlicer
|
||||
boundMethods []string
|
||||
boundStructs map[string]*ParsedStruct
|
||||
boundStructPointerLiterals []string
|
||||
boundVariables slicer.StringSlicer
|
||||
|
||||
variableFunctionDecls map[string]string
|
||||
variableStructDecls map[string]string
|
||||
|
||||
internalMethods slicer.StringSlicer
|
||||
|
||||
structCache map[string]*ParsedStruct
|
||||
structPointerFunctionDecls map[string]string
|
||||
structFunctionDecls map[string]string
|
||||
}
|
||||
|
||||
type ParsedParameter struct {
|
||||
Name string
|
||||
Type string
|
||||
IsArray bool
|
||||
}
|
||||
|
||||
func (p *ParsedParameter) JSType() string {
|
||||
return string(goTypeToJS(p.Type))
|
||||
}
|
||||
|
||||
type ParsedMethod struct {
|
||||
Struct string
|
||||
Name string
|
||||
Comments []string
|
||||
Inputs []*ParsedParameter
|
||||
Returns []*ParsedParameter
|
||||
}
|
||||
|
||||
// InputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Typescript
|
||||
func (m *ParsedMethod) 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, ", ")
|
||||
}
|
||||
|
||||
// InputsAsJSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *ParsedMethod) InputsAsJSText() string {
|
||||
var inputs []string
|
||||
|
||||
for _, input := range m.Inputs {
|
||||
inputs = append(inputs, input.Name)
|
||||
}
|
||||
|
||||
return strings.Join(inputs, ", ")
|
||||
}
|
||||
|
||||
// OutputsAsTSText generates a string with the method inputs
|
||||
// formatted in a way acceptable to Javascript
|
||||
func (m *ParsedMethod) 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, ", ")
|
||||
}
|
||||
|
||||
type ParsedStruct struct {
|
||||
Name string
|
||||
Methods []*ParsedMethod
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
return &Parser{
|
||||
variableFunctionDecls: make(map[string]string),
|
||||
variableStructDecls: make(map[string]string),
|
||||
internalMethods: *slicer.String([]string{"WailsInit", "WailsShutdown"}),
|
||||
structCache: make(map[string]*ParsedStruct),
|
||||
structPointerFunctionDecls: make(map[string]string),
|
||||
structFunctionDecls: make(map[string]string),
|
||||
boundStructs: make(map[string]*ParsedStruct),
|
||||
}
|
||||
}
|
||||
|
||||
func parseProject(projectPath string) ([]*Package, error) {
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedName |
|
||||
packages.NeedFiles |
|
||||
packages.NeedSyntax |
|
||||
packages.NeedTypes |
|
||||
packages.NeedTypesInfo,
|
||||
}
|
||||
pkgs, err := packages.Load(cfg, projectPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Problem loading packages")
|
||||
}
|
||||
if packages.PrintErrors(pkgs) > 0 {
|
||||
return nil, errors.Wrap(err, "Errors during parsing")
|
||||
}
|
||||
|
||||
var result []*Package
|
||||
|
||||
p := NewParser()
|
||||
|
||||
// Iterate the packages
|
||||
for _, pkg := range pkgs {
|
||||
|
||||
thisPackage, err := p.parsePackage(pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k := range p.structCache {
|
||||
thisPackage.Structs = append(thisPackage.Structs, p.structCache[k])
|
||||
}
|
||||
|
||||
result = append(result, thisPackage)
|
||||
|
||||
}
|
||||
|
||||
// Resolve links between data
|
||||
err = p.Resolve()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parsePackage(pkg *packages.Package) (*Package, error) {
|
||||
result := &Package{Name: pkg.Name}
|
||||
|
||||
for _, file := range pkg.Syntax {
|
||||
err := p.parseFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Parser) parseFile(file *ast.File) error {
|
||||
ast.Inspect(file, func(n ast.Node) bool {
|
||||
switch x := n.(type) {
|
||||
// Parse import declarations
|
||||
case *ast.ImportSpec:
|
||||
// Determine what wails has been imported as
|
||||
if x.Path.Value == `"github.com/wailsapp/wails/v2"` {
|
||||
p.wailsPkgVar = x.Name.Name
|
||||
}
|
||||
// Parse calls. We are looking for app.Bind() calls
|
||||
case *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.appVarName && f.Sel.Name == "Bind" {
|
||||
if len(x.Args) == 1 {
|
||||
ce, ok := x.Args[0].(*ast.CallExpr)
|
||||
if ok {
|
||||
n, ok := ce.Fun.(*ast.Ident)
|
||||
if ok {
|
||||
// We found a bind method using a function call
|
||||
// EG: app.Bind( newMyStruct() )
|
||||
p.boundMethods = append(p.boundMethods, n.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.boundStructPointerLiterals = append(p.boundStructPointerLiterals, 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.boundStructLiterals.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.boundVariables.Add(i.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We scan assignments for a number of reasons:
|
||||
// * Determine the variable containing the main application
|
||||
// * Determine the type of variables that get used in Bind()
|
||||
// * Determine the type of variables that get created with var := &MyStruct{}
|
||||
case *ast.AssignStmt:
|
||||
for _, rhs := range x.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.wailsPkgVar {
|
||||
// Check we are calling a function to create the app
|
||||
if se.Sel.Name == "CreateApp" || se.Sel.Name == "CreateAppWithOptions" {
|
||||
if len(x.Lhs) == 1 {
|
||||
i, ok := x.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Found the app variable name
|
||||
p.appVarName = i.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check for function assignment
|
||||
// a := newMyStruct()
|
||||
fe, ok := ce.Fun.(*ast.Ident)
|
||||
if ok {
|
||||
if len(x.Lhs) == 1 {
|
||||
i, ok := x.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
// Store the variable -> Function mapping
|
||||
// so we can later resolve the type
|
||||
p.variableFunctionDecls[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(x.Lhs) == 1 {
|
||||
i, ok := x.Lhs[0].(*ast.Ident)
|
||||
if ok {
|
||||
p.variableStructDecls[i.Name] = t.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We scan for functions to build up a list of function names
|
||||
// for a number of reasons:
|
||||
// * Determine which functions are struct methods that are bound
|
||||
// * Determine
|
||||
case *ast.FuncDecl:
|
||||
if x.Recv != nil {
|
||||
// This is a struct method
|
||||
for _, field := range x.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(x.Name.Name) {
|
||||
continue
|
||||
}
|
||||
// If we haven't already found this struct,
|
||||
// Create a placeholder in the cache
|
||||
parsedStruct := p.structCache[i.Name]
|
||||
if parsedStruct == nil {
|
||||
p.structCache[i.Name] = &ParsedStruct{
|
||||
Name: i.Name,
|
||||
}
|
||||
parsedStruct = p.structCache[i.Name]
|
||||
}
|
||||
|
||||
// If this method is Public
|
||||
if string(x.Name.Name[0]) == strings.ToUpper((string(x.Name.Name[0]))) {
|
||||
structMethod := &ParsedMethod{
|
||||
Struct: i.Name,
|
||||
Name: x.Name.Name,
|
||||
}
|
||||
// Check if the method has comments.
|
||||
// If so, save it with the parsed method
|
||||
if x.Doc != nil {
|
||||
for _, comment := range x.Doc.List {
|
||||
stringComment := strings.TrimPrefix(comment.Text, "//")
|
||||
structMethod.Comments = append(structMethod.Comments, strings.TrimSpace(stringComment))
|
||||
}
|
||||
}
|
||||
|
||||
// Save the input parameters
|
||||
for _, inputField := range x.Type.Params.List {
|
||||
t, ok := inputField.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, name := range inputField.Names {
|
||||
structMethod.Inputs = append(structMethod.Inputs, &ParsedParameter{Name: name.Name, Type: t.Name})
|
||||
}
|
||||
}
|
||||
|
||||
// Save the output parameters
|
||||
if x.Type.Results != nil {
|
||||
|
||||
for _, outputField := range x.Type.Results.List {
|
||||
// Check for basic types
|
||||
t, ok := outputField.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
// Check for arrays
|
||||
a, ok := outputField.Type.(*ast.ArrayType)
|
||||
if ok {
|
||||
// spew.Dump(a)
|
||||
ident := a.Elt.(*ast.Ident)
|
||||
if len(outputField.Names) == 0 {
|
||||
structMethod.Returns = append(structMethod.Returns, &ParsedParameter{Type: ident.Name, IsArray: true})
|
||||
} else {
|
||||
for _, name := range outputField.Names {
|
||||
structMethod.Returns = append(structMethod.Returns, &ParsedParameter{Name: name.Name, Type: ident.Name, IsArray: true})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(outputField.Names) == 0 {
|
||||
structMethod.Returns = append(structMethod.Returns, &ParsedParameter{Type: t.Name})
|
||||
} else {
|
||||
for _, name := range outputField.Names {
|
||||
structMethod.Returns = append(structMethod.Returns, &ParsedParameter{Name: name.Name, Type: t.Name})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 := x.Name.Name
|
||||
|
||||
// Look for one that returns a single value
|
||||
if x.Type != nil && x.Type.Results != nil && x.Type.Results.List != nil {
|
||||
if len(x.Type.Results.List) == 1 {
|
||||
// Check for *struct
|
||||
t, ok := x.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.structPointerFunctionDecls[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 := x.Type.Results.List[0].Type.(*ast.Ident)
|
||||
if ok {
|
||||
// println("*** Function", functionName, "found which returns: "+t.Name)
|
||||
p.structFunctionDecls[functionName] = t.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
// spew.Dump(file)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) Resolve() error {
|
||||
// Resolve bound Methods
|
||||
for _, method := range p.boundMethods {
|
||||
s, ok := p.structPointerFunctionDecls[method]
|
||||
if !ok {
|
||||
s, ok = p.structFunctionDecls[method]
|
||||
if !ok {
|
||||
return fmt.Errorf("bind statement using " + method + " but cannot find " + method + " declaration")
|
||||
} else {
|
||||
return fmt.Errorf("cannot bind struct using method `" + method + "` because it returns a struct (" + s + "). Return a pointer to " + s + " instead.")
|
||||
}
|
||||
}
|
||||
structDefinition := p.structCache[s]
|
||||
if structDefinition == nil {
|
||||
return fmt.Errorf("Fatal: Bind statement using `" + method + "` but cannot find struct " + s + " definition")
|
||||
}
|
||||
p.boundStructs[s] = structDefinition
|
||||
}
|
||||
|
||||
// Resolve bound vars
|
||||
for _, structLiteral := range p.boundStructPointerLiterals {
|
||||
s, ok := p.structCache[structLiteral]
|
||||
if !ok {
|
||||
return fmt.Errorf("bind statement using " + structLiteral + " but cannot find " + structLiteral + " declaration")
|
||||
}
|
||||
p.boundStructs[structLiteral] = s
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Resolve bound variables
|
||||
p.boundVariables.Each(func(variable string) {
|
||||
v, ok := p.variableStructDecls[variable]
|
||||
if !ok {
|
||||
method, ok := p.variableFunctionDecls[variable]
|
||||
if !ok {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("bind statement using variable `" + variable + "` which does not resolve to a struct pointer")
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve function name
|
||||
v, ok = p.structPointerFunctionDecls[method]
|
||||
if !ok {
|
||||
v, ok = p.structFunctionDecls[method]
|
||||
if !ok {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("bind statement using " + method + " but cannot find " + method + " declaration")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("cannot bind variable `" + variable + "` because it resolves to a struct (" + v + "). Return a pointer to " + v + " instead.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s, ok := p.structCache[v]
|
||||
if !ok {
|
||||
println("Fatal: Bind statement using variable `" + variable + "` which resolves to a `" + v + "` but cannot find its declaration")
|
||||
os.Exit(1)
|
||||
}
|
||||
p.boundStructs[v] = s
|
||||
})
|
||||
|
||||
// Return first error when resolving bound variables
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check for struct literals
|
||||
if p.boundStructLiterals.Length() > 0 {
|
||||
return fmt.Errorf("cannot bind structs using struct literals. Create a pointer to the struct instead: %s", p.boundStructLiterals.Join(", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
135
v2/pkg/commands/build/internal/backendjs/struct.go
Normal file
135
v2/pkg/commands/build/internal/backendjs/struct.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package backendjs
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"os"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// Struct defines a parsed struct
|
||||
type Struct struct {
|
||||
Name string
|
||||
Comments []string
|
||||
Fields []*Field
|
||||
}
|
||||
|
||||
type StructName struct {
|
||||
Name string
|
||||
Package string
|
||||
}
|
||||
|
||||
// Field defines a parsed struct field
|
||||
type Field struct {
|
||||
Name string
|
||||
Type string
|
||||
Struct *StructName
|
||||
Comments []string
|
||||
}
|
||||
|
||||
// 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 parseStruct(structType *ast.StructType, name string) (*Struct, error) {
|
||||
result := &Struct{Name: name}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
result.Fields = append(result.Fields, parseField(field)...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseField(field *ast.Field) []*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:
|
||||
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,
|
||||
}
|
||||
|
||||
default:
|
||||
FieldNotSupported(t)
|
||||
}
|
||||
|
||||
// Loop over names
|
||||
for _, name := range field.Names {
|
||||
|
||||
// Create a field per name
|
||||
thisField := &Field{
|
||||
Comments: 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)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import "reflect"
|
||||
|
||||
type Struct struct {
|
||||
Name string
|
||||
Fields []*Field
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
Type reflect.Value
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package backendjs
|
||||
|
||||
import "reflect"
|
||||
|
||||
var BoolValue bool = true
|
||||
var IntValue int = 0
|
||||
var Int8Value int8 = 0
|
||||
var Int16Value int16 = 0
|
||||
var Int32Value int32 = 0
|
||||
var Int64Value int64 = 0
|
||||
var UintValue uint = 0
|
||||
var Uint8Value uint8 = 0
|
||||
var Uint16Value uint16 = 0
|
||||
var Uint32Value uint32 = 0
|
||||
var Uint64Value uint64 = 0
|
||||
var UintptrValue uintptr = 0
|
||||
var Float32Value float32 = 0
|
||||
var Float64Value float64 = 0
|
||||
var Complex64Value complex64 = 0
|
||||
var Complex128Value complex128 = 0
|
||||
var StringValue string = ""
|
||||
|
||||
type Person struct {
|
||||
Name string
|
||||
Age uint8
|
||||
}
|
||||
|
||||
type GuestList struct {
|
||||
People []*Person
|
||||
}
|
||||
|
||||
func NewPerson(name string, age uint8) reflect.Value {
|
||||
return reflect.New(reflect.TypeOf(&Person{
|
||||
Name: name,
|
||||
Age: age,
|
||||
}))
|
||||
}
|
||||
|
||||
var Bool = reflect.New(reflect.TypeOf(BoolValue))
|
||||
var Int = reflect.New(reflect.TypeOf(IntValue))
|
||||
var Int8 = reflect.New(reflect.TypeOf(Int8Value))
|
||||
var Int16 = reflect.New(reflect.TypeOf(Int16Value))
|
||||
var Int32 = reflect.New(reflect.TypeOf(Int32Value))
|
||||
var Int64 = reflect.New(reflect.TypeOf(Int64Value))
|
||||
var Uint = reflect.New(reflect.TypeOf(UintValue))
|
||||
var Uint8 = reflect.New(reflect.TypeOf(Uint8Value))
|
||||
var Uint16 = reflect.New(reflect.TypeOf(Uint16Value))
|
||||
var Uint32 = reflect.New(reflect.TypeOf(Uint32Value))
|
||||
var Uint64 = reflect.New(reflect.TypeOf(Uint64Value))
|
||||
var Uintptr = reflect.New(reflect.TypeOf(UintptrValue))
|
||||
var Float32 = reflect.New(reflect.TypeOf(Float32Value))
|
||||
var Float64 = reflect.New(reflect.TypeOf(Float64Value))
|
||||
var Complex64 = reflect.New(reflect.TypeOf(Complex64Value))
|
||||
var Complex128 = reflect.New(reflect.TypeOf(Complex128Value))
|
||||
var String = reflect.New(reflect.TypeOf(StringValue))
|
||||
Reference in New Issue
Block a user