Js package generation (#554)

* WIP

* Generation of index.js

* Add RelativeToCwd

* Add JSDoc comments

* Convert to ES6 syntax

* Fix typo

* Initial generation of typescript declarations

* Typescript improvements

* Improved @returns jsdoc

* Improved declaration files

* Simplified output

* Rename file

* Tidy up

* Revert "Simplified output"

This reverts commit 15cdf7382b21a15a36616adbca13c9b034a1907b.

* Now parsing actual code

* Support Array types

* Reimagined parser

* Wrap parsing in Parser

* Rewritten module generator (TS Only)

* Final touches

* Slight refactor to improve output

* Struct comments. External struct literal binding

* Reworked project parser *working*

* remove debug info

* Refactor of parser

* remove the spew

* Better Ts support

* Better project generation logic

* Support local functions in bind()

* JS Object generation. Linting.

* Support json tags in module generation

* Updated mod files

* Support vscode file generation

* Better global.d.ts

* add ts-check to templates

* Support TS declaration files

* improved 'generate' command for module
This commit is contained in:
Lea Anthony
2020-11-15 09:25:38 +11:00
committed by GitHub
parent ddb875f788
commit 62374b9b53
42 changed files with 2405 additions and 38 deletions
+1
View File
@@ -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
@@ -0,0 +1,91 @@
package generate
import (
"io"
"time"
"github.com/leaanthony/clir"
"github.com/wailsapp/wails/v2/pkg/clilogger"
"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("module", "Generates a JS module for the frontend to interface with the backend")
// Quiet Init
quiet := false
backendAPI.BoolFlag("q", "Supress output to console", &quiet)
backendAPI.Action(func() error {
// Create logger
logger := clilogger.New(w)
logger.Mute(quiet)
app.PrintBanner()
logger.Print("Generating Javascript module for Go code...")
// Start Time
start := time.Now()
p, err := parser.GenerateWailsFrontendPackage()
if err != nil {
return err
}
logger.Println("done.")
logger.Println("")
elapsed := time.Since(start)
packages := p.Packages
// Print report
for _, pkg := range p.Packages {
if pkg.ShouldGenerate() {
logPackage(pkg, logger)
}
}
logger.Println("%d packages parsed in %s.", len(packages), elapsed)
return nil
})
return nil
}
func logPackage(pkg *parser.Package, logger *clilogger.CLILogger) {
logger.Println("Processed Go package '" + pkg.Gopackage.Name + "' as '" + pkg.Name + "'")
for _, strct := range pkg.Structs() {
logger.Println("")
logger.Println(" Processed struct '" + strct.Name + "'")
if strct.IsBound {
for _, method := range strct.Methods {
logger.Println(" Bound method '" + method.Name + "'")
}
}
if strct.IsUsedAsData {
for _, field := range strct.Fields {
if !field.Ignored {
logger.Print(" Processed ")
if field.IsOptional {
logger.Print("optional ")
}
logger.Println("field '" + field.Name + "' as '" + field.JSName() + "'")
}
}
}
}
logger.Println("")
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
// logger.Println(" Original Go Package Path:", pkg.Gopackage.PkgPath)
}
@@ -32,13 +32,17 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
command.StringFlag("n", "Name of project", &projectName)
// Setup project directory
projectDirectory := "."
projectDirectory := ""
command.StringFlag("d", "Project directory", &projectDirectory)
// Quiet Init
quiet := false
command.BoolFlag("q", "Supress output to console", &quiet)
// VSCode project files
vscode := false
command.BoolFlag("vscode", "Generate VSCode project files", &vscode)
// List templates
list := false
command.BoolFlag("l", "List templates", &list)
@@ -83,10 +87,11 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error {
// Create Template Options
options := &templates.Options{
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
ProjectName: projectName,
TargetDir: projectDirectory,
TemplateName: templateName,
Logger: logger,
GenerateVSCode: vscode,
}
return initProject(options)
@@ -110,6 +115,14 @@ func initProject(options *templates.Options) error {
// Output stats
elapsed := time.Since(start)
options.Logger.Println("")
options.Logger.Println("Project Name: " + options.ProjectName)
options.Logger.Println("Project Directory: " + options.TargetDir)
options.Logger.Println("Project Template: " + options.TemplateName)
options.Logger.Println("")
if options.GenerateVSCode {
options.Logger.Println("VSCode config files generated.")
}
options.Logger.Println("")
options.Logger.Println(fmt.Sprintf("Initialised project '%s' in %s.", options.ProjectName, elapsed.Round(time.Millisecond).String()))
options.Logger.Println("")
+6
View File
@@ -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())
+6 -2
View File
@@ -3,19 +3,23 @@ module github.com/wailsapp/wails/v2
go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/fatih/structtag v1.2.0
github.com/fsnotify/fsnotify v1.4.9
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
github.com/pkg/errors v0.9.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/minify/v2 v2.9.5
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/xyproto/xpm v1.2.1
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c
golang.org/x/tools v0.0.0-20200902012652-d1954cc86c82
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
nhooyr.io/websocket v1.8.6
+7 -12
View File
@@ -1,8 +1,8 @@
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -44,13 +44,12 @@ github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU
github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0=
github.com/leaanthony/gosod v0.0.4 h1:v4hepo4IyL8E8c9qzDsvYcA0KGh7Npf8As74K5ibQpI=
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=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
@@ -63,23 +62,19 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tdewolff/minify v1.1.0 h1:nxHQi1ML+g3ZbZHffiZ6eC7vMqNvSRfX3KB5Y5y/kfw=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs=
github.com/tdewolff/minify/v2 v2.9.5 h1:+fHvqLencVdv14B+zgxQGhetF9qXl/nRTN/1mcyQwpM=
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
github.com/tdewolff/parse v1.1.0 h1:tMjj9GCK8zzwjWyxdZ4pabzdWO1VG+G3bvCnG6aUIyQ=
github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38=
github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ=
github.com/tdewolff/parse/v2 v2.5.3 h1:fnPIstKgEfxd3+wwHnH73sAYydsR0o/jYhcQ6c5PkrA=
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+2
View File
@@ -68,6 +68,8 @@ func getMethods(value interface{}) ([]*BoundMethod, error) {
boundMethod.Inputs = inputs
// Iterate outputs
// TODO: Determine what to do about limiting return types
// especially around errors.
outputParamCount := methodType.NumOut()
var outputs []*Parameter
for outputIndex := 0; outputIndex < outputParamCount; outputIndex++ {
+31
View File
@@ -20,6 +20,17 @@ func LocalDirectory() string {
return filepath.Dir(thisFile)
}
// RelativeToCwd returns an absolute path based on the cwd
// and the given relative path
func RelativeToCwd(relativePath string) (string, error) {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, relativePath), nil
}
// Mkdir will create the given directory
func Mkdir(dirname string) error {
return os.Mkdir(dirname, 0755)
@@ -169,3 +180,23 @@ func GetSubdirectories(rootDir string) (*slicer.StringSlicer, error) {
})
return &result, err
}
func DirIsEmpty(dir string) (bool, error) {
if !DirExists(dir) {
return false, fmt.Errorf("DirIsEmpty called with a non-existant directory: %s", dir)
}
// CREDIT: https://stackoverflow.com/a/30708914/8325411
f, err := os.Open(dir)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1) // Or f.Readdir(1)
if err == io.EOF {
return true, nil
}
return false, err // Either not empty or error, suits both cases
}
-1
View File
@@ -65,7 +65,6 @@ func ParseProject(projectPath string) (BoundStructs, error) {
var wailsPkgVar = ""
ast.Inspect(file, func(n ast.Node) bool {
var s string
switch x := n.(type) {
// Parse import declarations
case *ast.ImportSpec:
@@ -0,0 +1,29 @@
package operatingsystem
import (
"fmt"
"golang.org/x/sys/windows/registry"
)
func platformInfo() (*OS, error) {
// Default value
var result OS
result.ID = "Unknown"
result.Name = "Windows"
result.Version = "Unknown"
// Credit: https://stackoverflow.com/a/33288328
// Ignore errors as it isn't a showstopper
key, _ := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
defer key.Close()
fmt.Printf("%+v\n", key)
// Ignore errors as it isn't a showstopper
productName, _, _ := key.GetStringValue("ProductName")
fmt.Println(productName)
return nil, nil
}
+15 -9
View File
@@ -2,14 +2,20 @@
package system
import (
"fmt"
"syscall"
)
import "github.com/wailsapp/wails/v2/internal/system/operatingsystem"
func (i *Info) discover() {
dll := syscall.MustLoadDLL("kernel32.dll")
p := dll.MustFindProc("GetVersion")
v, _, _ := p.Call()
fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
func (i *Info) discover() error {
var err error
osinfo, err := operatingsystem.Info()
if err != nil {
return err
}
i.OS = osinfo
// dll := syscall.MustLoadDLL("kernel32.dll")
// p := dll.MustFindProc("GetVersion")
// v, _, _ := p.Call()
// fmt.Printf("Windows version %d.%d (Build %d)\n", byte(v), uint8(v>>8), uint16(v>>16))
return nil
}
@@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Wails: Debug {{.ProjectName}} (Desktop)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToDesktopBinary}}",
"preLaunchTask": "build_desktop",
"cwd": "",
"env": {},
"args": []
},
{
"name": "Wails: Debug {{.ProjectName}} (Server)",
"type": "go",
"request": "launch",
"mode": "exec",
"program": "${workspaceFolder}/{{.PathToServerBinary}}",
"preLaunchTask": "build_server",
"cwd": "",
"env": {},
"args": []
},
]
}
@@ -0,0 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build_desktop",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build"
},
{
"label": "build_server",
"type": "shell",
"options": {
"cwd": "{{.TargetDir}}"
},
"command": "wails build -t server"
},
]
}
+69 -9
View File
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/leaanthony/gosod"
@@ -31,11 +32,14 @@ type Data struct {
// Options for installing a template
type Options struct {
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
ProjectName string
TemplateName string
BinaryName string
TargetDir string
Logger *clilogger.CLILogger
GenerateVSCode bool
PathToDesktopBinary string
PathToServerBinary string
}
// Template holds data relating to a template
@@ -162,9 +166,24 @@ func Install(options *Options) error {
}
// Did the user want to install in current directory?
if options.TargetDir == "." {
// Yes - use cwd
options.TargetDir = cwd
if options.TargetDir == "" {
// If the current directory is empty, use it
isEmpty, err := fs.DirIsEmpty(cwd)
if err != nil {
return err
}
if isEmpty {
// Yes - use cwd
options.TargetDir = cwd
} else {
options.TargetDir = filepath.Join(cwd, options.ProjectName)
if fs.DirExists(options.TargetDir) {
return fmt.Errorf("cannot create project directory. Dir exists: %s", options.TargetDir)
}
}
} else {
// Get the absolute path of the given directory
targetDir, err := filepath.Abs(filepath.Join(cwd, options.TargetDir))
@@ -213,7 +232,11 @@ func Install(options *Options) error {
return err
}
// Calculate the directory name
err = generateIDEFiles(options)
if err != nil {
return err
}
return nil
}
@@ -243,3 +266,40 @@ func OutputList(logger *clilogger.CLILogger) error {
table.Render()
return nil
}
func generateIDEFiles(options *Options) error {
if options.GenerateVSCode {
return generateVSCodeFiles(options)
}
return nil
}
func generateVSCodeFiles(options *Options) error {
targetDir := filepath.Join(options.TargetDir, ".vscode")
sourceDir := fs.RelativePath(filepath.Join("./ides/vscode"))
// Use Gosod to install the template
installer, err := gosod.TemplateDir(sourceDir)
if err != nil {
return err
}
binaryName := filepath.Base(options.TargetDir)
if runtime.GOOS == "windows" {
// yay windows
binaryName += ".exe"
}
options.PathToDesktopBinary = filepath.Join("build", runtime.GOOS, "desktop", binaryName)
options.PathToServerBinary = filepath.Join("build", runtime.GOOS, "server", binaryName)
err = installer.Extract(targetDir, options)
if err != nil {
return err
}
return nil
}
+9
View File
@@ -8,6 +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/parser"
)
// Mode is the type used to indicate the build modes
@@ -89,6 +90,13 @@ func Build(options *Options) (string, error) {
// Initialise Builder
builder.SetProjectData(projectData)
// Generate Frontend JS Package
outputLogger.Println(" - Generating Backend JS Package")
// Ignore the parser report coming back
_, err = parser.GenerateWailsFrontendPackage()
if err != nil {
return "", err
}
if !options.IgnoreFrontend {
outputLogger.Println(" - Building Wails Frontend")
err = builder.BuildFrontend(outputLogger)
@@ -115,6 +123,7 @@ func Build(options *Options) (string, error) {
return "", err
}
outputLogger.Println("done.")
// Do we need to pack the app?
if options.Pack {
@@ -0,0 +1,6 @@
package build
func packageApplication(options *Options) error {
// TBD
return nil
}
+50
View 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
}
+21
View File
@@ -0,0 +1,21 @@
package parser
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
}
+156
View File
@@ -0,0 +1,156 @@
package parser
import (
"fmt"
"strings"
"github.com/leaanthony/slicer"
)
// 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 *Field) string {
switch input.Type {
case "string":
return "string"
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return "number"
case "float32", "float64":
return "number"
case "bool":
return "boolean"
// case reflect.Array, reflect.Slice:
// return JsArray
// case reflect.Ptr, reflect.Struct, reflect.Map, reflect.Interface:
// return JsObject
case "struct":
return input.Struct.Name
default:
fmt.Printf("Unsupported input to goTypeToJS: %+v", input)
return "*"
}
}
// 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":
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.Name != "" {
if input.Struct.Package.Name != pkgName {
result = input.Struct.Package.Name + "."
}
}
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:
fmt.Printf("Unsupported input to goTypeToTS: %+v", input)
return JsUnsupported
}
if input.IsArray {
result = result + "[]"
}
return result
}
func goTypeToTSDeclaration(input *Field, pkgName string) 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.Name != "" {
if input.Struct.Package.Name != pkgName {
result = `import("./_` + input.Struct.Package.Name + `").`
}
}
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:
fmt.Printf("Unsupported input to goTypeToTS: %+v", input)
return JsUnsupported
}
if input.IsArray {
result = result + "[]"
}
return result
}
func isUnresolvedType(typeName string) bool {
switch typeName {
case "string":
return false
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return false
case "float32", "float64":
return false
case "bool":
return false
case "struct":
return false
default:
return true
}
}
var reservedJSWords []string = []string{"abstract", "arguments", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield", "Array", "Date", "eval", "function", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"}
var jsReservedWords *slicer.StringSlicer = slicer.String(reservedJSWords)
func isJSReservedWord(input string) bool {
return jsReservedWords.Contains(input)
}
func startsWithLowerCaseLetter(input string) bool {
firstLetter := string(input[0])
return strings.ToLower(firstLetter) == firstLetter
}
+311
View File
@@ -0,0 +1,311 @@
package parser
import (
"fmt"
"go/ast"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/fatih/structtag"
)
// Field defines a parsed struct field
type Field 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
// Indicates if the Field is an array of type "Type"
IsArray bool
// JSON field name defined by a json tag
JSONOptions
}
type JSONOptions struct {
Name string
IsOptional bool
Ignored bool
}
// JSType returns the Javascript type for this field
func (f *Field) JSType() string {
return string(goTypeToJS(f))
}
// JSName returns the Javascript name for this field
func (f *Field) JSName() string {
if f.JSONOptions.Name != "" {
return f.JSONOptions.Name
}
return f.Name
}
// TSName returns the Typescript name for this field
func (f *Field) TSName() string {
result := f.Name
if f.JSONOptions.Name != "" {
result = f.JSONOptions.Name
}
if f.IsOptional {
result += "?"
}
return result
}
// AsTSDeclaration returns a TS definition of a single type field
func (f *Field) AsTSDeclaration(pkgName string) string {
return f.TSName() + ": " + f.TypeAsTSType(pkgName)
}
// NameForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) NameForPropertyDoc() string {
if f.IsOptional {
return "[" + f.JSName() + "]"
}
return f.JSName()
}
// TypeForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) TypeForPropertyDoc() string {
result := goTypeToJS(f)
if f.IsArray {
result += "[]"
}
return result
}
// 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 strct *Struct
var isArray bool
var jsonOptions JSONOptions
// Determine type
switch t := field.Type.(type) {
case *ast.Ident:
fieldType = t.Name
unresolved := isUnresolvedType(fieldType)
// Check if this type is actually a struct
if unresolved {
// Assume it is a struct
// Parse the struct
var err error
strct, err = p.parseStruct(pkg, t.Name)
if err != nil {
return nil, err
}
if strct == nil {
fieldName := "<anonymous>"
if len(field.Names) > 0 {
fieldName = field.Names[0].Name
}
return nil, fmt.Errorf("unresolved type in field %s: %s", fieldName, fieldType)
}
fieldType = "struct"
}
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
}
}
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
}
}
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)
}
// Parse json tag if available
if field.Tag != nil {
err := parseJSONOptions(field.Tag.Value, &jsonOptions)
if err != nil {
return nil, err
}
}
// Loop over names if we have
if len(field.Names) > 0 {
for _, name := range field.Names {
// TODO: Check field names are valid in JS
if isJSReservedWord(name.Name) {
return nil, fmt.Errorf("unable to use field name %s - reserved word in Javascript", name.Name)
}
// Create a field per name
thisField := &Field{
Comments: parseComments(field.Doc),
}
thisField.Name = name.Name
thisField.Type = fieldType
thisField.Struct = strct
thisField.IsArray = isArray
thisField.JSONOptions = jsonOptions
result = append(result, thisField)
}
return result, nil
}
// When we have no name
thisField := &Field{
Comments: parseComments(field.Doc),
}
thisField.Type = fieldType
thisField.Struct = strct
thisField.IsArray = isArray
result = append(result, thisField)
return result, nil
}
func parseJSONOptions(fieldTag string, jsonOptions *JSONOptions) error {
// Remove backticks
fieldTag = strings.Trim(fieldTag, "`")
// Parse the tag
tags, err := structtag.Parse(fieldTag)
if err != nil {
return err
}
jsonTag, err := tags.Get("json")
if err != nil {
return err
}
if jsonTag == nil {
return nil
}
// Save the name
jsonOptions.Name = jsonTag.Name
// Check if this field is ignored
if jsonTag.Name == "-" {
jsonOptions.Ignored = true
}
// Check if this field is optional
if jsonTag.HasOption("omitempty") {
jsonOptions.IsOptional = true
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More