5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-03 03:52:41 +08:00
wails/v3/internal/parser/bindings.go
Lea Anthony 26530a0f5a Fix reserve word check.
Add model generation.
Warn if field is unexported in the Go struct
2023-03-07 19:48:30 +11:00

286 lines
7.0 KiB
Go

package parser
import (
"sort"
"strconv"
"strings"
"github.com/samber/lo"
)
const header = `// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
`
const helperTemplate = `function {{structName}}(method) {
return {
packageName: "{{packageName}}",
serviceName: "{{structName}}",
methodName: method,
args: Array.prototype.slice.call(arguments, 1),
};
}
`
func GenerateHelper(packageName, structName string) string {
result := strings.ReplaceAll(helperTemplate, "{{packageName}}", packageName)
result = strings.ReplaceAll(result, "{{structName}}", structName)
return result
}
const bindingTemplate = `
/**
* {{structName}}.{{methodName}}
* Comments
* @param name {string}
* @returns {Promise<string>}
**/
function {{methodName}}({{inputs}}) {
return wails.Call({{structName}}("{{methodName}}"{{args}}));
}
`
var reservedWords = []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",
"object",
}
func sanitiseJSVarName(name string) string {
// if the name is a reserved word, prefix with an
// underscore
if lo.Contains(reservedWords, name) {
return "_" + name
}
return name
}
func GenerateBinding(structName string, method *BoundMethod) (string, []string) {
var models []string
result := strings.ReplaceAll(bindingTemplate, "{{structName}}", structName)
result = strings.ReplaceAll(result, "{{methodName}}", method.Name)
comments := strings.TrimSpace(method.DocComment)
result = strings.ReplaceAll(result, "Comments", comments)
var params string
for _, input := range method.Inputs {
pkgName := getPackageName(input)
if pkgName != "" {
models = append(models, pkgName)
}
params += " * @param " + sanitiseJSVarName(input.Name) + " {" + input.JSType() + "}\n"
}
params = strings.TrimSuffix(params, "\n")
if len(params) == 0 {
params = " *"
}
result = strings.ReplaceAll(result, " * @param name {string}", params)
var inputs string
for _, input := range method.Inputs {
pkgName := getPackageName(input)
if pkgName != "" {
models = append(models, pkgName)
}
inputs += sanitiseJSVarName(input.Name) + ", "
}
inputs = strings.TrimSuffix(inputs, ", ")
args := inputs
if len(args) > 0 {
args = ", " + args
}
result = strings.ReplaceAll(result, "{{inputs}}", inputs)
result = strings.ReplaceAll(result, "{{args}}", args)
// outputs
var returns string
if len(method.Outputs) == 0 {
returns = " * @returns {Promise<void>}"
} else {
returns = " * @returns {Promise<"
for _, output := range method.Outputs {
pkgName := getPackageName(output)
if pkgName != "" {
models = append(models, pkgName)
}
jsType := output.JSType()
if jsType == "error" {
jsType = "void"
}
returns += jsType + ", "
}
returns = strings.TrimSuffix(returns, ", ")
returns += ">}"
}
result = strings.ReplaceAll(result, " * @returns {Promise<string>}", returns)
return result, lo.Uniq(models)
}
func getPackageName(input *Parameter) string {
if !input.Type.IsStruct {
return ""
}
result := input.Type.Package
if result == "" {
result = "main"
}
return result
}
func normalisePackageNames(packageNames []string) map[string]string {
// We iterate over the package names and determine if any of them
// have a forward slash. If this is the case, we assume that the
// package name is the last element of the path. If this has already
// been found, then we need to add a digit to the end of the package
// name to make it unique. We return a map of the original package
// name to the new package name.
var result = make(map[string]string)
var packagesConverted = make(map[string]struct{})
var count = 1
for _, packageName := range packageNames {
var originalPackageName = packageName
if strings.Contains(packageName, "/") {
parts := strings.Split(packageName, "/")
packageName = parts[len(parts)-1]
}
if _, ok := packagesConverted[packageName]; ok {
// We've already seen this package name. Add a digit
// to the end of the package name to make it unique
count += 1
packageName += strconv.Itoa(count)
}
packagesConverted[packageName] = struct{}{}
result[originalPackageName] = packageName
}
return result
}
func GenerateBindings(bindings map[string]map[string][]*BoundMethod) map[string]string {
var result = make(map[string]string)
var normalisedPackageNames = normalisePackageNames(lo.Keys(bindings))
// sort the bindings keys
packageNames := lo.Keys(bindings)
sort.Strings(packageNames)
for _, packageName := range packageNames {
var allModels []string
packageBindings := bindings[packageName]
structNames := lo.Keys(packageBindings)
sort.Strings(structNames)
for _, structName := range structNames {
result[normalisedPackageNames[packageName]] += GenerateHelper(normalisedPackageNames[packageName], structName)
methods := packageBindings[structName]
sort.Slice(methods, func(i, j int) bool {
return methods[i].Name < methods[j].Name
})
for _, method := range methods {
thisBinding, models := GenerateBinding(structName, method)
result[normalisedPackageNames[packageName]] += thisBinding
allModels = append(allModels, models...)
}
}
result[normalisedPackageNames[packageName]] += `
window.go = window.go || {};
`
// Iterate over the sorted struct keys
result[normalisedPackageNames[packageName]] += "window.go." + normalisedPackageNames[packageName] + " = {\n"
for _, structName := range structNames {
result[normalisedPackageNames[packageName]] += " " + structName + ": {\n"
methods := packageBindings[structName]
sort.Slice(methods, func(i, j int) bool {
return methods[i].Name < methods[j].Name
})
for _, method := range methods {
result[normalisedPackageNames[packageName]] += " " + method.Name + ",\n"
}
result[normalisedPackageNames[packageName]] += " },\n"
}
result[normalisedPackageNames[packageName]] += "};\n"
// add imports
if len(allModels) > 0 {
allModels := lo.Uniq(allModels)
var models []string
for _, model := range allModels {
models = append(models, normalisedPackageNames[model])
}
sort.Strings(models)
result[normalisedPackageNames[packageName]] += "\n"
imports := "import {" + strings.Join(models, ", ") + "} from './models';\n"
result[normalisedPackageNames[packageName]] = imports + "\n" + result[normalisedPackageNames[packageName]]
}
result[normalisedPackageNames[packageName]] = header + result[normalisedPackageNames[packageName]]
}
return result
}