mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 04:59:38 +08:00
532 lines
14 KiB
Go
532 lines
14 KiB
Go
package parser
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/samber/lo"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
var Debug = false
|
|
|
|
func debug(msg string, args ...interface{}) {
|
|
if Debug {
|
|
println(fmt.Sprintf(msg, args...))
|
|
}
|
|
}
|
|
|
|
type parsedPackage struct {
|
|
name string
|
|
pkg *ast.Package
|
|
boundStructs map[string]*ast.TypeSpec
|
|
}
|
|
|
|
type Context struct {
|
|
packages map[string]*parsedPackage
|
|
}
|
|
|
|
func (c *Context) GetBoundStructs() map[string][]*ast.TypeSpec {
|
|
structs := make(map[string][]*ast.TypeSpec)
|
|
for _, pkg := range c.packages {
|
|
for _, structType := range pkg.boundStructs {
|
|
structs[pkg.name] = append(structs[pkg.name], structType)
|
|
}
|
|
}
|
|
return structs
|
|
}
|
|
|
|
func ParseDirectory(dir string) (*Context, error) {
|
|
// Parse the directory
|
|
fset := token.NewFileSet()
|
|
if dir == "." || dir == "" {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dir = cwd
|
|
}
|
|
println("Parsing directory " + dir)
|
|
pkgs, err := parser.ParseDir(fset, dir, nil, parser.AllErrors)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
context := &Context{
|
|
packages: make(map[string]*parsedPackage),
|
|
}
|
|
|
|
// Iterate through the packages
|
|
for _, pkg := range pkgs {
|
|
context.packages[pkg.Name] = &parsedPackage{
|
|
name: pkg.Name,
|
|
pkg: pkg,
|
|
boundStructs: make(map[string]*ast.TypeSpec),
|
|
}
|
|
}
|
|
|
|
findApplicationNewCalls(context)
|
|
|
|
return context, nil
|
|
}
|
|
|
|
func findApplicationNewCalls(context *Context) {
|
|
// Iterate through the packages
|
|
currentPackages := lo.Keys(context.packages)
|
|
|
|
for _, packageName := range currentPackages {
|
|
thisPackage := context.packages[packageName]
|
|
debug("Parsing package: %s", packageName)
|
|
// Iterate through the package's files
|
|
for _, file := range thisPackage.pkg.Files {
|
|
// Use an ast.Inspector to find the calls to application.New
|
|
ast.Inspect(file, func(n ast.Node) bool {
|
|
// Check if the node is a call expression
|
|
callExpr, ok := n.(*ast.CallExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Check if the function being called is "application.New"
|
|
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if selExpr.Sel.Name != "New" {
|
|
return true
|
|
}
|
|
if id, ok := selExpr.X.(*ast.Ident); !ok || id.Name != "application" {
|
|
return true
|
|
}
|
|
|
|
// Check there is only 1 argument
|
|
if len(callExpr.Args) != 1 {
|
|
return true
|
|
}
|
|
|
|
// Check argument 1 is a struct literal
|
|
structLit, ok := callExpr.Args[0].(*ast.CompositeLit)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
// Check struct literal is of type "options.Application"
|
|
selectorExpr, ok := structLit.Type.(*ast.SelectorExpr)
|
|
if !ok {
|
|
return true
|
|
}
|
|
if selectorExpr.Sel.Name != "Application" {
|
|
return true
|
|
}
|
|
if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "options" {
|
|
return true
|
|
}
|
|
|
|
for _, elt := range structLit.Elts {
|
|
// Find the "Bind" field
|
|
kvExpr, ok := elt.(*ast.KeyValueExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if id, ok := kvExpr.Key.(*ast.Ident); !ok || id.Name != "Bind" {
|
|
continue
|
|
}
|
|
// Check the value is a slice of interfaces
|
|
sliceExpr, ok := kvExpr.Value.(*ast.CompositeLit)
|
|
if !ok {
|
|
continue
|
|
}
|
|
var arrayType *ast.ArrayType
|
|
if arrayType, ok = sliceExpr.Type.(*ast.ArrayType); !ok {
|
|
continue
|
|
}
|
|
|
|
// Check array type is of type "interface{}"
|
|
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
|
|
continue
|
|
}
|
|
// Iterate through the slice elements
|
|
for _, elt := range sliceExpr.Elts {
|
|
// Check the element is a unary expression
|
|
unaryExpr, ok := elt.(*ast.UnaryExpr)
|
|
if ok {
|
|
// Check the unary expression is a composite lit
|
|
boundStructLit, ok := unaryExpr.X.(*ast.CompositeLit)
|
|
if !ok {
|
|
continue
|
|
}
|
|
// Check if the composite lit is a struct
|
|
if _, ok := boundStructLit.Type.(*ast.StructType); ok {
|
|
// Parse struct
|
|
continue
|
|
}
|
|
// Check if the lit is an ident
|
|
ident, ok := boundStructLit.Type.(*ast.Ident)
|
|
if ok {
|
|
if ident.Obj == nil {
|
|
structTypeSpec := findStructInPackage(thisPackage.pkg, ident.Name)
|
|
thisPackage.boundStructs[ident.Name] = structTypeSpec
|
|
findNestedStructs(structTypeSpec, file, packageName, context)
|
|
continue
|
|
}
|
|
// Check if the ident is a struct type
|
|
if t, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
|
thisPackage.boundStructs[ident.Name] = t
|
|
findNestedStructs(t, file, packageName, context)
|
|
continue
|
|
}
|
|
// Check the typespec decl is a struct
|
|
if _, ok := ident.Obj.Decl.(*ast.StructType); ok {
|
|
continue
|
|
}
|
|
|
|
}
|
|
// Check if the lit is a selector
|
|
selector, ok := boundStructLit.Type.(*ast.SelectorExpr)
|
|
if ok {
|
|
getStructsFromSelector(selector, file, context)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func getStructsFromSelector(selector *ast.SelectorExpr, file *ast.File, context *Context) {
|
|
debug("getStructsFromSelector called with selector '%s' on file '%s.go'", selector.Sel.Name, file.Name.Name)
|
|
|
|
// extract package name from selector
|
|
packageName := selector.X.(*ast.Ident).Name
|
|
|
|
if context.packages[packageName] == nil {
|
|
context.packages[packageName] = &parsedPackage{
|
|
name: packageName,
|
|
boundStructs: make(map[string]*ast.TypeSpec),
|
|
}
|
|
}
|
|
|
|
// extract struct name from selector
|
|
structName := selector.Sel.Name
|
|
|
|
// Find the package name from the imports
|
|
for _, imp := range file.Imports {
|
|
var match bool
|
|
if imp.Name == nil || imp.Name.Name == packageName {
|
|
match = true
|
|
}
|
|
if match == false {
|
|
pathSplit := strings.Split(imp.Path.Value, "/")
|
|
endPath := strings.Trim(pathSplit[len(pathSplit)-1], `"`)
|
|
match = endPath == packageName
|
|
}
|
|
|
|
if match {
|
|
// We have the import
|
|
cfg := &packages.Config{
|
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedModule,
|
|
}
|
|
pkgs, err := packages.Load(cfg, strings.Trim(imp.Path.Value, `"`))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
foundPackage := pkgs[0]
|
|
|
|
// Iterate the files in the package and find struct types
|
|
for _, parsedFile := range foundPackage.Syntax {
|
|
ast.Inspect(parsedFile, func(n ast.Node) bool {
|
|
if n == nil {
|
|
return false
|
|
}
|
|
switch n.(type) {
|
|
case *ast.TypeSpec:
|
|
typeSpec := n.(*ast.TypeSpec)
|
|
if typeSpec.Name.Name == structName {
|
|
if _, ok := context.packages[packageName].boundStructs[structName]; !ok {
|
|
debug("Adding struct '%s' in package '%s'", structName, packageName)
|
|
context.packages[packageName].boundStructs[typeSpec.Name.Name] = typeSpec
|
|
findNestedStructs(typeSpec, parsedFile, packageName, context)
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func findNestedStructs(t *ast.TypeSpec, parsedFile *ast.File, pkgName string, context *Context) {
|
|
debug("findNestedStructs called with type '%s' on file '%s.go'", t.Name.Name, parsedFile.Name.Name)
|
|
structType, ok := t.Type.(*ast.StructType)
|
|
if !ok {
|
|
return
|
|
}
|
|
for _, field := range structType.Fields.List {
|
|
for _, ident := range field.Names {
|
|
switch t := ident.Obj.Decl.(*ast.Field).Type.(type) {
|
|
case *ast.Ident:
|
|
if t.Obj == nil {
|
|
continue
|
|
}
|
|
if t.Obj.Kind == ast.Typ {
|
|
if _, ok := t.Obj.Decl.(*ast.TypeSpec); ok {
|
|
if _, ok := context.packages[pkgName].boundStructs[t.Name]; !ok {
|
|
debug("Adding nested struct '%s' to package '%s'", t.Name, pkgName)
|
|
context.packages[pkgName].boundStructs[t.Name] = t.Obj.Decl.(*ast.TypeSpec)
|
|
findNestedStructs(t.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
|
}
|
|
}
|
|
}
|
|
case *ast.SelectorExpr:
|
|
if ident, ok := t.X.(*ast.Ident); ok {
|
|
if ident.IsExported() {
|
|
getStructsFromSelector(t, parsedFile, context)
|
|
}
|
|
}
|
|
case *ast.StarExpr:
|
|
if sel, ok := t.X.(*ast.SelectorExpr); ok {
|
|
if _, ok := sel.X.(*ast.Ident); ok {
|
|
if ident.IsExported() {
|
|
getStructsFromSelector(sel, parsedFile, context)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
findStructsInMethods(t.Name.Name, parsedFile, pkgName, context)
|
|
|
|
}
|
|
|
|
func findStructsInMethods(name string, parsedFile *ast.File, pkgName string, context *Context) {
|
|
debug("findStructsInMethods called with type '%s' on file '%s.go'", name, parsedFile.Name.Name)
|
|
// Find the struct declaration for the given name
|
|
var structDecl *ast.TypeSpec
|
|
for _, decl := range parsedFile.Decls {
|
|
if fn, ok := decl.(*ast.FuncDecl); ok {
|
|
// check the receiver name is the same as the name given
|
|
if fn.Recv == nil {
|
|
continue
|
|
}
|
|
// Check if the receiver is a pointer
|
|
if starExpr, ok := fn.Recv.List[0].Type.(*ast.StarExpr); ok {
|
|
if ident, ok := starExpr.X.(*ast.Ident); ok {
|
|
if ident.Name != name {
|
|
continue
|
|
}
|
|
}
|
|
} else {
|
|
if ident, ok := fn.Recv.List[0].Type.(*ast.Ident); ok {
|
|
if ident.Name != name {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
findStructsInMethodParams(fn, parsedFile, pkgName, context)
|
|
}
|
|
}
|
|
if structDecl == nil {
|
|
return
|
|
}
|
|
// Iterate the methods in the struct
|
|
|
|
}
|
|
|
|
func findStructsInMethodParams(f *ast.FuncDecl, parsedFile *ast.File, pkgName string, context *Context) {
|
|
debug("findStructsInMethodParams called with type '%s' on file '%s.go'", f.Name.Name, parsedFile.Name.Name)
|
|
if f.Type.Params == nil {
|
|
for _, field := range f.Type.Params.List {
|
|
parseField(field, parsedFile, pkgName, context)
|
|
}
|
|
}
|
|
if f.Type.Results != nil {
|
|
for _, field := range f.Type.Results.List {
|
|
parseField(field, parsedFile, pkgName, context)
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseField(field *ast.Field, parsedFile *ast.File, pkgName string, context *Context) {
|
|
if se, ok := field.Type.(*ast.StarExpr); ok {
|
|
// Check if the star expr is a struct
|
|
if selExp, ok := se.X.(*ast.SelectorExpr); ok {
|
|
getStructsFromSelector(selExp, parsedFile, context)
|
|
return
|
|
}
|
|
if ident, ok := se.X.(*ast.Ident); ok {
|
|
if ident.Obj == nil {
|
|
return
|
|
}
|
|
if ident.Obj.Kind == ast.Typ {
|
|
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
|
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
|
|
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
|
|
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
|
|
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
|
} else {
|
|
debug("Struct %s already bound", ident.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if selExp, ok := field.Type.(*ast.SelectorExpr); ok {
|
|
getStructsFromSelector(selExp, parsedFile, context)
|
|
return
|
|
}
|
|
if ident, ok := field.Type.(*ast.Ident); ok {
|
|
if ident.Obj == nil {
|
|
return
|
|
}
|
|
if ident.Obj.Kind == ast.Typ {
|
|
if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok {
|
|
if _, ok := context.packages[pkgName].boundStructs[ident.Name]; !ok {
|
|
debug("Adding field struct '%s' to package '%s'", ident.Name, pkgName)
|
|
context.packages[pkgName].boundStructs[ident.Name] = ident.Obj.Decl.(*ast.TypeSpec)
|
|
findNestedStructs(ident.Obj.Decl.(*ast.TypeSpec), parsedFile, pkgName, context)
|
|
} else {
|
|
debug("Struct %s already bound", ident.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func findStructInPackage(pkg *ast.Package, name string) *ast.TypeSpec {
|
|
for _, file := range pkg.Files {
|
|
for _, decl := range file.Decls {
|
|
if gen, ok := decl.(*ast.GenDecl); ok && gen.Tok == token.TYPE {
|
|
for _, spec := range gen.Specs {
|
|
if typeSpec, ok := spec.(*ast.TypeSpec); ok {
|
|
if typeSpec.Name.Name == name {
|
|
if _, ok := typeSpec.Type.(*ast.StructType); ok {
|
|
return typeSpec
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Package struct {
|
|
Name string
|
|
Specs []*ast.TypeSpec
|
|
}
|
|
|
|
var goToTS = map[string]string{
|
|
"int": "number",
|
|
"int8": "number",
|
|
"int16": "number",
|
|
"int32": "number",
|
|
"int64": "number",
|
|
"uint": "number",
|
|
"uint8": "number",
|
|
"uint16": "number",
|
|
"uint32": "number",
|
|
"uint64": "number",
|
|
"float32": "number",
|
|
"float64": "number",
|
|
"string": "string",
|
|
"bool": "boolean",
|
|
}
|
|
|
|
//func GenerateModels(specs map[string][]*ast.TypeSpec) ([]byte, error) {
|
|
// var buf bytes.Buffer
|
|
// var packages []Package
|
|
// for pkg, pkgSpecs := range specs {
|
|
// packages = append(packages, Package{Name: pkg, Specs: pkgSpecs})
|
|
// }
|
|
// sort.Slice(packages, func(i, j int) bool { return packages[i].Name < packages[j].Name })
|
|
// for _, pkg := range packages {
|
|
// if _, err := fmt.Fprintf(&buf, "namespace %s {\n", pkg.Name); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// sort.Slice(pkg.Specs, func(i, j int) bool { return pkg.Specs[i].Name.Name < pkg.Specs[j].Name.Name })
|
|
// for _, spec := range pkg.Specs {
|
|
// if structType, ok := spec.Type.(*ast.StructType); ok {
|
|
// if _, err := fmt.Fprintf(&buf, " class %s {\n", spec.Name.Name); err != nil {
|
|
// return nil, err
|
|
// }
|
|
//
|
|
// for _, field := range structType.Fields.List {
|
|
//
|
|
// // Get the Go type of the field
|
|
// goType := types.ExprString(field.Type)
|
|
// // Look up the corresponding TypeScript type
|
|
// tsType, ok := goToTS[goType]
|
|
// if !ok {
|
|
// tsType = goType
|
|
// }
|
|
//
|
|
// if _, err := fmt.Fprintf(&buf, " %s: %s;\n", field.Names[0].Name, tsType); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// }
|
|
//
|
|
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// if _, err := fmt.Fprintf(&buf, " }\n"); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// if _, err := fmt.Fprintf(&buf, "}\n"); err != nil {
|
|
// return nil, err
|
|
// }
|
|
// }
|
|
// return buf.Bytes(), nil
|
|
//}
|
|
|
|
type allModels struct {
|
|
known map[string]map[string]struct{}
|
|
}
|
|
|
|
func newAllModels(models map[string][]*ast.TypeSpec) *allModels {
|
|
result := &allModels{known: make(map[string]map[string]struct{})}
|
|
// iterate over all models
|
|
for pkg, pkgSpecs := range models {
|
|
for _, spec := range pkgSpecs {
|
|
result.known[pkg] = make(map[string]struct{})
|
|
result.known[pkg][spec.Name.Name] = struct{}{}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (k *allModels) exists(name string) bool {
|
|
// Split the name into package and type
|
|
parts := strings.Split(name, ".")
|
|
typ := parts[0]
|
|
pkg := "main"
|
|
if len(parts) == 2 {
|
|
pkg = parts[0]
|
|
typ = parts[1]
|
|
}
|
|
|
|
knownPkg, ok := k.known[pkg]
|
|
if !ok {
|
|
return false
|
|
}
|
|
_, ok = knownPkg[typ]
|
|
return ok
|
|
}
|