mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-05 23:12:03 +08:00
341 lines
9.0 KiB
Go
341 lines
9.0 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"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 bindingTemplate = `
|
|
/**Comments
|
|
* @function {{methodName}}* @param names {string}
|
|
* @returns {Promise<string>}
|
|
**/
|
|
`
|
|
|
|
const callByID = `export function {{methodName}}({{inputs}}) {
|
|
return wails.CallByID({{ID}}, ...Array.prototype.slice.call(arguments, 0));
|
|
}
|
|
`
|
|
|
|
const callByName = `export function {{methodName}}({{inputs}}) {
|
|
return wails.CallByName("{{Name}}", ...Array.prototype.slice.call(arguments, 0));
|
|
}
|
|
`
|
|
|
|
const enumTemplate = `
|
|
export enum {{.EnumName}} {
|
|
{{.EnumValues}}
|
|
}
|
|
`
|
|
|
|
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
|
|
}
|
|
|
|
type ExternalStruct struct {
|
|
Package string
|
|
Name string
|
|
}
|
|
|
|
func GenerateBinding(thisStructName string, method *BoundMethod, useIDs bool) (string, []string, map[packagePath]map[string]*ExternalStruct) {
|
|
var externalStructs = make(map[packagePath]map[string]*ExternalStruct)
|
|
var models []string
|
|
template := bindingTemplate
|
|
if useIDs {
|
|
template += callByID
|
|
} else {
|
|
template += callByName
|
|
}
|
|
result := strings.ReplaceAll(template, "{{structName}}", thisStructName)
|
|
result = strings.ReplaceAll(result, "{{methodName}}", method.Name)
|
|
result = strings.ReplaceAll(result, "{{ID}}", fmt.Sprintf("%v", method.ID))
|
|
|
|
// get last part of method.Package path
|
|
parts := strings.Split(method.Package, "/")
|
|
packageName := parts[len(parts)-1]
|
|
|
|
result = strings.ReplaceAll(result, "{{Name}}", fmt.Sprintf("%v.%v.%v", packageName, thisStructName, method.Name))
|
|
comments := strings.TrimSpace(method.DocComment)
|
|
if comments != "" {
|
|
comments = "\n * " + comments
|
|
}
|
|
result = strings.ReplaceAll(result, "Comments", comments)
|
|
var params string
|
|
for _, input := range method.Inputs {
|
|
inputName := sanitiseJSVarName(input.Name)
|
|
pkgName := getPackageName(input)
|
|
if pkgName != "" {
|
|
models = append(models, pkgName)
|
|
}
|
|
if input.Type.IsStruct || input.Type.IsEnum {
|
|
if _, ok := externalStructs[input.Type.Package]; !ok {
|
|
externalStructs[input.Type.Package] = make(map[string]*ExternalStruct)
|
|
}
|
|
externalStructs[input.Type.Package][input.Type.Name] = &ExternalStruct{
|
|
Package: input.Type.Package,
|
|
Name: input.Type.Name,
|
|
}
|
|
}
|
|
|
|
inputType := input.JSType(packageName)
|
|
params += "\n * @param " + inputName + " {" + inputType + "}"
|
|
}
|
|
params = strings.TrimSuffix(params, "\n")
|
|
//if len(params) > 0 {
|
|
// params = "\n" + params
|
|
//}
|
|
result = strings.ReplaceAll(result, "* @param names {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(packageName)
|
|
if jsType == "error" {
|
|
jsType = "void"
|
|
}
|
|
if output.Type.IsStruct {
|
|
if _, ok := externalStructs[output.Type.Package]; !ok {
|
|
externalStructs[output.Type.Package] = make(map[string]*ExternalStruct)
|
|
}
|
|
externalStructs[output.Type.Package][output.Type.Name] = &ExternalStruct{
|
|
Package: output.Type.Package,
|
|
Name: output.Type.Name,
|
|
}
|
|
jsType = output.NamespacedStructVariable(output.Type.Package)
|
|
}
|
|
returns += jsType + ", "
|
|
}
|
|
returns = strings.TrimSuffix(returns, ", ")
|
|
returns += ">}"
|
|
}
|
|
result = strings.ReplaceAll(result, " * @returns {Promise<string>}", returns)
|
|
|
|
return result, lo.Uniq(models), externalStructs
|
|
}
|
|
|
|
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 (p *Project) GenerateBindings(bindings map[string]map[string][]*BoundMethod, useIDs bool) map[string]map[string]string {
|
|
|
|
var result = make(map[string]map[string]string)
|
|
|
|
// 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)
|
|
relativePackageDir := p.RelativePackageDir(packageName)
|
|
_ = relativePackageDir
|
|
sort.Strings(structNames)
|
|
for _, structName := range structNames {
|
|
if _, ok := result[relativePackageDir]; !ok {
|
|
result[relativePackageDir] = make(map[string]string)
|
|
}
|
|
methods := packageBindings[structName]
|
|
sort.Slice(methods, func(i, j int) bool {
|
|
return methods[i].Name < methods[j].Name
|
|
})
|
|
var allNamespacedStructs map[packagePath]map[string]*ExternalStruct
|
|
var namespacedStructs map[packagePath]map[string]*ExternalStruct
|
|
var thisBinding string
|
|
var models []string
|
|
for _, method := range methods {
|
|
thisBinding, models, namespacedStructs = GenerateBinding(structName, method, useIDs)
|
|
// Merge the namespaced structs
|
|
allNamespacedStructs = mergeNamespacedStructs(allNamespacedStructs, namespacedStructs)
|
|
allModels = append(allModels, models...)
|
|
result[relativePackageDir][structName] += thisBinding
|
|
}
|
|
|
|
if len(allNamespacedStructs) > 0 {
|
|
thisPkg := p.packageCache[packageName]
|
|
typedefs := "/**\n"
|
|
for externalPackageName, namespacedStruct := range allNamespacedStructs {
|
|
pkgInfo := p.packageCache[externalPackageName]
|
|
relativePackageDir := p.RelativeBindingsDir(thisPkg, pkgInfo)
|
|
namePrefix := ""
|
|
if pkgInfo.Name != "" && pkgInfo.Path != thisPkg.Path {
|
|
namePrefix = pkgInfo.Name
|
|
}
|
|
|
|
// Get keys from namespacedStruct and iterate over them in sorted order
|
|
namespacedStructNames := lo.Keys(namespacedStruct)
|
|
sort.Strings(namespacedStructNames)
|
|
for _, thisStructName := range namespacedStructNames {
|
|
structInfo := namespacedStruct[thisStructName]
|
|
typedefs += " * @typedef {import('" + relativePackageDir + "/models')." + thisStructName + "} " + namePrefix + structInfo.Name + "\n"
|
|
}
|
|
}
|
|
typedefs += " */\n"
|
|
result[relativePackageDir][structName] = typedefs + result[relativePackageDir][structName]
|
|
}
|
|
result[relativePackageDir][structName] = header + result[relativePackageDir][structName]
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func mergeNamespacedStructs(structs map[packagePath]map[string]*ExternalStruct, structs2 map[packagePath]map[string]*ExternalStruct) map[packagePath]map[string]*ExternalStruct {
|
|
if structs == nil {
|
|
structs = make(map[packagePath]map[string]*ExternalStruct)
|
|
}
|
|
for pkg, pkgStructs := range structs2 {
|
|
if _, ok := structs[pkg]; !ok {
|
|
structs[pkg] = make(map[string]*ExternalStruct)
|
|
}
|
|
for name, structInfo := range pkgStructs {
|
|
structs[pkg][name] = structInfo
|
|
}
|
|
}
|
|
return structs
|
|
}
|