diff --git a/v3/internal/parser/README.md b/v3/internal/parser/README.md index 59de0d749..7b24c47d1 100644 --- a/v3/internal/parser/README.md +++ b/v3/internal/parser/README.md @@ -7,12 +7,46 @@ This package contains the static analyser used for parsing Wails projects so tha ## Implemented -- [x] Parsing of structs -- [x] Generation of models - - [x] Scalars - - [x] Arrays +- [ ] Parsing of bound methods + - [x] Method names + - [ ] Method parameters + - [x] Zero parameters + - [x] Single input parameter + - [x] Single output parameter + - [x] Multiple input parameters + - [x] Multiple output parameters + - [x] Named output parameters + - [x] int + - [x] Pointer + - [x] uint + - [ ] Pointer + - [x] float + - [ ] Pointer + - [x] string + - [ ] Pointer + - [x] bool + - [ ] Pointer + - [ ] Struct + - [x] Pointer + - [x] Slices + - [ ] Pointer + - [x] Recursive + - [x] Maps + - [ ] Pointer +- [ ] Model Parsing + - [x] In same package + - [ ] In different package + - [ ] Multiple named fields, e.g. one,two,three string + - [ ] Scalars + - [ ] Arrays - [ ] Maps - - [x] Structs + - [ ] Structs + - [ ] Anonymous structs +- [ ] Generation of models + - [ ] Scalars + - [ ] Arrays + - [ ] Maps + - [ ] Structs - [ ] Generation of bindings ## Limitations diff --git a/v3/internal/parser/parser.go b/v3/internal/parser/parser.go index f4b93ed72..36752280b 100644 --- a/v3/internal/parser/parser.go +++ b/v3/internal/parser/parser.go @@ -6,19 +6,33 @@ import ( "go/parser" "go/token" "os" + "path/filepath" ) var packageCache = make(map[string]*ParsedPackage) +var structCache = make(map[string]map[structName]*StructDef) type packageName = string type structName = string -type Parameter struct { +type StructDef struct { + Name string + DocComment string + Fields []*Field +} + +type ParameterType struct { Name string - Type string IsStruct bool IsSlice bool IsPointer bool + MapKey *ParameterType + MapValue *ParameterType +} + +type Parameter struct { + Name string + Type *ParameterType } type BoundMethod struct { @@ -28,32 +42,25 @@ type BoundMethod struct { Outputs []*Parameter } -type Model struct { - Name string - Fields []*Field -} - type Field struct { - Name string - Type string - IsStruct bool - IsSlice bool + Name string + Type *ParameterType } type ParsedPackage struct { - Pkg *ast.Package + Pkg *ast.Package + Name string + Dir string } type Project struct { Path string BoundMethods map[packageName]map[structName][]*BoundMethod - Models map[packageName]map[structName]*Model } func ParseProject(projectPath string) (*Project, error) { result := &Project{ BoundMethods: make(map[packageName]map[structName][]*BoundMethod), - Models: make(map[packageName]map[structName]*Model), } pkgs, err := result.parseDirectory(projectPath) if err != nil { @@ -88,9 +95,13 @@ func (p *Project) parseDirectory(dir string) (map[string]*ParsedPackage, error) } var result = make(map[string]*ParsedPackage) for packageName, pkg := range pkgs { - parsedPackage := &ParsedPackage{Pkg: pkg} + parsedPackage := &ParsedPackage{ + Pkg: pkg, + Dir: getDirectoryForPackage(pkg), + } packageCache[dir] = parsedPackage result[packageName] = parsedPackage + structCache[packageName] = make(map[structName]*StructDef) } return result, nil } @@ -287,8 +298,12 @@ func (p *Project) parseBoundStructMethods(name string, pkg *ast.Package) error { Outputs: make([]*Parameter, 0), } - method.Inputs = p.parseParameters(funcDecl.Type.Params) - method.Outputs = p.parseParameters(funcDecl.Type.Results) + if funcDecl.Type.Params != nil { + method.Inputs = p.parseParameters(funcDecl.Type.Params, pkg) + } + if funcDecl.Type.Results != nil { + method.Outputs = p.parseParameters(funcDecl.Type.Results, pkg) + } methods = append(methods, method) } @@ -300,7 +315,7 @@ func (p *Project) parseBoundStructMethods(name string, pkg *ast.Package) error { return nil } -func (p *Project) parseParameters(params *ast.FieldList) []*Parameter { +func (p *Project) parseParameters(params *ast.FieldList, pkg *ast.Package) []*Parameter { var result []*Parameter for _, field := range params.List { var theseFields []*Parameter @@ -317,20 +332,99 @@ func (p *Project) parseParameters(params *ast.FieldList) []*Parameter { } // loop over fields for _, thisField := range theseFields { - thisField.Type = getTypeString(field.Type) - switch t := field.Type.(type) { - case *ast.StarExpr: - thisField.IsStruct = isStructType(t.X) - thisField.IsPointer = true - case *ast.StructType: - thisField.IsStruct = true - case *ast.ArrayType: - thisField.IsSlice = true - thisField.IsStruct = isStructType(t.Elt) - case *ast.MapType: - thisField.IsSlice = true - thisField.IsStruct = isStructType(t.Value) + thisField.Type = p.parseParameterType(field, pkg) + result = append(result, thisField) + } + } + return result +} + +func (p *Project) parseParameterType(field *ast.Field, pkg *ast.Package) *ParameterType { + var result ParameterType + result.Name = getTypeString(field.Type) + switch t := field.Type.(type) { + case *ast.StarExpr: + result.IsStruct = isStructType(t.X) + result.IsPointer = true + case *ast.StructType: + result.IsStruct = true + case *ast.ArrayType: + result.IsSlice = true + result.IsStruct = isStructType(t.Elt) + case *ast.MapType: + tempfield := &ast.Field{Type: t.Key} + result.MapKey = p.parseParameterType(tempfield, pkg) + tempfield.Type = t.Value + result.MapValue = p.parseParameterType(tempfield, pkg) + default: + } + if result.IsStruct { + _, ok := structCache[pkg.Name][result.Name] + if !ok { + p.getStructDef(result.Name, pkg) + } + } + return &result +} + +func (p *Project) getStructDef(name string, pkg *ast.Package) { + _, ok := structCache[pkg.Name][name] + if ok { + return + } + // Iterate over all files in the package + for _, file := range pkg.Files { + // Iterate over all declarations in the file + for _, decl := range file.Decls { + // Check if the declaration is a type declaration + if typeDecl, ok := decl.(*ast.GenDecl); ok { + // Check if the type declaration is a struct type + if typeDecl.Tok == token.TYPE { + for _, spec := range typeDecl.Specs { + if typeSpec, ok := spec.(*ast.TypeSpec); ok { + if structType, ok := typeSpec.Type.(*ast.StructType); ok { + if typeSpec.Name.Name == name { + result := &StructDef{ + Name: name, + DocComment: typeDecl.Doc.Text(), + } + structCache[pkg.Name][name] = result + result.Fields = p.parseStructFields(structType, pkg) + } + } + } + } + } } + } + } +} + +func (p *Project) parseStructFields(structType *ast.StructType, pkg *ast.Package) []*Field { + var result []*Field + for _, field := range structType.Fields.List { + var theseFields []*Field + if len(field.Names) > 0 { + for _, name := range field.Names { + theseFields = append(theseFields, &Field{ + Name: name.Name, + }) + } + } else { + theseFields = append(theseFields, &Field{ + Name: "", + }) + } + // loop over fields + for _, thisField := range theseFields { + paramType := p.parseParameterType(field, pkg) + if paramType.IsStruct { + _, ok := structCache[pkg.Name][paramType.Name] + if !ok { + p.getStructDef(paramType.Name, pkg) + } + } + thisField.Type = paramType result = append(result, thisField) } } @@ -345,8 +439,8 @@ func getTypeString(expr ast.Expr) string { return getTypeString(t.X) case *ast.ArrayType: return getTypeString(t.Elt) - //case *ast.MapType: - // return "map[" + getTypeString(t.Key) + "]" + getTypeString(t.Value) + case *ast.MapType: + return "map" default: return "any" } @@ -370,3 +464,10 @@ func isStructType(expr ast.Expr) bool { return false } } + +func getDirectoryForPackage(pkg *ast.Package) string { + for _, file := range pkg.Files { + return filepath.Dir(file.Name.Name) + } + return "" +} diff --git a/v3/internal/parser/parser_old.go b/v3/internal/parser/parser_old.go deleted file mode 100644 index 90f2ce029..000000000 --- a/v3/internal/parser/parser_old.go +++ /dev/null @@ -1,758 +0,0 @@ -package parser - -// -//import ( -// "fmt" -// "go/ast" -// "go/build" -// "go/parser" -// "go/token" -// "os" -// "path/filepath" -// "strconv" -// "strings" -// -// "github.com/samber/lo" -//) -// -//var Debug = false -// -//func debug(msg string, args ...interface{}) { -// if Debug { -// println(fmt.Sprintf(msg, args...)) -// } -//} -// -//type BoundStruct struct { -// Name string -// MethodDecls []Method -//} -// -//type parsedPackage struct { -// name string -// pkg *ast.Package -// boundStructs map[string]*BoundStruct -// boundStructMethods map[string][]Method -//} -// -//type Context struct { -// packages map[string]*parsedPackage -// dir string -//} -// -//func (c *Context) findImportPackage(pkgName string, pkg *ast.Package) (*ast.Package, error) { -// for _, file := range pkg.Files { -// for _, imp := range file.Imports { -// path, err := strconv.Unquote(imp.Path.Value) -// if err != nil { -// return nil, err -// } -// if imp.Name != nil && imp.Name.Name == pkgName { -// return c.getPackageFromPath(path) -// } else { -// _, pkgName := filepath.Split(path) -// if pkgName == pkgName { -// return c.getPackageFromPath(path) -// } -// } -// } -// } -// return nil, fmt.Errorf("Package %s not found in %s", pkgName, pkg.Name) -//} -// -//func (c *Context) getPackageFromPath(path string) (*ast.Package, error) { -// dir, err := filepath.Abs(c.dir) -// if err != nil { -// return nil, err -// } -// if !filepath.IsAbs(path) { -// dir = filepath.Join(dir, path) -// } else { -// impPkgDir, err := build.Import(path, dir, build.ImportMode(0)) -// if err != nil { -// return nil, err -// } -// dir = impPkgDir.Dir -// } -// impPkg, err := parser.ParseDir(token.NewFileSet(), dir, nil, parser.AllErrors) -// if err != nil { -// return nil, err -// } -// for impName, impPkg := range impPkg { -// if impName == "main" { -// continue -// } -// return impPkg, nil -// } -// return nil, fmt.Errorf("Package not found in imported package %s", path) -//} -// -//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{ -// dir: dir, -// 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]*BoundStruct), -// boundStructMethods: make(map[string][]Method), -// } -// } -// -// findApplicationNewCalls(context) -// err = findStructDefinitions(context) -// if err != nil { -// return nil, err -// } -// -// return context, nil -//} -// -//func findStructDefinitions(context *Context) error { -// // iterate over the packages -// for _, pkg := range context.packages { -// // iterate the struct names -// for structName, _ := range pkg.boundStructs { -// structSpec, methods := getStructTypeSpec(pkg.pkg, structName) -// if structSpec == nil { -// return fmt.Errorf("unable to find struct %s in package %s", structName, pkg.name) -// } -// pkg.boundStructs[structName] = &BoundStruct{ -// Name: structName, -// MethodDecls: methods, -// } -// } -// } -// return nil -//} -// -//func findApplicationNewCalls(context *Context) { -// println("Finding application.New calls") -// // 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 "application.Options" -// selectorExpr, ok := structLit.Type.(*ast.SelectorExpr) -// if !ok { -// return true -// } -// if selectorExpr.Sel.Name != "Options" { -// return true -// } -// if id, ok := selectorExpr.X.(*ast.Ident); !ok || id.Name != "application" { -// 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 { -// thisPackage.boundStructs[ident.Name] = &BoundStruct{ -// Name: ident.Name, -// } -// continue -// } -// // Check if the ident is a struct type -// if _, ok := ident.Obj.Decl.(*ast.TypeSpec); ok { -// thisPackage.boundStructs[ident.Name] = &BoundStruct{ -// Name: ident.Name, -// } -// 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 { -// // Check if the selector is an ident -// if ident, ok := selector.X.(*ast.Ident); ok { -// // Check if the ident is a package -// if _, ok := context.packages[ident.Name]; !ok { -// externalPackage, err := context.getPackageFromPath(ident.Name) -// if err != nil { -// println("Error getting package from path: " + err.Error()) -// return true -// } -// context.packages[ident.Name] = &parsedPackage{ -// name: ident.Name, -// pkg: externalPackage, -// boundStructs: make(map[string]*BoundStruct), -// } -// } -// context.packages[ident.Name].boundStructs[selector.Sel.Name] = &BoundStruct{ -// Name: selector.Sel.Name, -// } -// } -// continue -// } -// } -// } -// } -// -// return true -// }) -// } -// } -//} -// -//type Method struct { -// Name string -// Type *ast.FuncType -//} -// - -// -////func findApplicationNewCalls(context *Context) { -//// println("Finding application.New calls") -//// // 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 "application.Options" -//// 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]*BoundStruct), -//// } -//// } -//// -//// // 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] = &BoundStruct{ -//// Name: typeSpec.Name.Name, -//// } -//// 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 -//} diff --git a/v3/internal/parser/parser_test.go b/v3/internal/parser/parser_test.go index e5b6d9398..2800c627a 100644 --- a/v3/internal/parser/parser_test.go +++ b/v3/internal/parser/parser_test.go @@ -25,18 +25,18 @@ func TestParseDirectory(t *testing.T) { DocComment: "Greet someone\n", Inputs: []*Parameter{ { - Name: "name", - Type: "string", - IsStruct: false, - IsSlice: false, + Name: "name", + Type: &ParameterType{ + Name: "string", + }, }, }, Outputs: []*Parameter{ { - Name: "", - Type: "string", - IsStruct: false, - IsSlice: false, + Name: "", + Type: &ParameterType{ + Name: "string", + }, }, }, }, @@ -46,10 +46,10 @@ func TestParseDirectory(t *testing.T) { Inputs: nil, Outputs: []*Parameter{ { - Name: "", - Type: "string", - IsStruct: false, - IsSlice: false, + Name: "", + Type: &ParameterType{ + Name: "string", + }, }, }, }, @@ -57,14 +57,18 @@ func TestParseDirectory(t *testing.T) { Name: "StringArrayInputStringOut", Inputs: []*Parameter{ { - Name: "in", - Type: "string", - IsSlice: true, + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, Outputs: []*Parameter{ { - Type: "string", + Type: &ParameterType{ + Name: "string", + }, }, }, }, @@ -72,15 +76,19 @@ func TestParseDirectory(t *testing.T) { Name: "StringArrayInputStringArrayOut", Inputs: []*Parameter{ { - Name: "in", - Type: "string", - IsSlice: true, + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, Outputs: []*Parameter{ { - Type: "string", - IsSlice: true, + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, }, @@ -88,16 +96,20 @@ func TestParseDirectory(t *testing.T) { Name: "StringArrayInputNamedOutput", Inputs: []*Parameter{ { - Name: "in", - Type: "string", - IsSlice: true, + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, Outputs: []*Parameter{ { - Name: "output", - Type: "string", - IsSlice: true, + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, }, @@ -105,20 +117,26 @@ func TestParseDirectory(t *testing.T) { Name: "StringArrayInputNamedOutputs", Inputs: []*Parameter{ { - Name: "in", - Type: "string", - IsSlice: true, + Name: "in", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, }, Outputs: []*Parameter{ { - Name: "output", - Type: "string", - IsSlice: true, + Name: "output", + Type: &ParameterType{ + Name: "string", + IsSlice: true, + }, }, { Name: "err", - Type: "error", + Type: &ParameterType{ + Name: "error", + }, }, }, }, @@ -126,20 +144,115 @@ func TestParseDirectory(t *testing.T) { Name: "IntPointerInputNamedOutputs", Inputs: []*Parameter{ { - Name: "in", - Type: "int", - IsPointer: true, + Name: "in", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, }, }, Outputs: []*Parameter{ { - Name: "output", - Type: "int", - IsPointer: true, + Name: "output", + Type: &ParameterType{ + Name: "int", + IsPointer: true, + }, }, { Name: "err", - Type: "error", + Type: &ParameterType{ + Name: "error", + }}, + }, + }, + { + Name: "IntInIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "int", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "int", + }, + }, + }, + }, + { + Name: "UIntInUIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "uint", + }, + }, + }, + }, + { + Name: "Float32InFloat32Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float32", + }, + }, + }, + }, + { + Name: "Float64InFloat64Out", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "float64", + }, + }, + }, + }, + { + Name: "BoolInBoolOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "bool", + }, + }, + }, + Outputs: []*Parameter{ + { + Type: &ParameterType{ + Name: "bool", + }, }, }, }, @@ -147,15 +260,19 @@ func TestParseDirectory(t *testing.T) { Name: "StructPointerInputErrorOutput", Inputs: []*Parameter{ { - Name: "in", - Type: "Person", - IsStruct: true, - IsPointer: true, + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + }, }, }, Outputs: []*Parameter{ { - Type: "error", + Type: &ParameterType{ + Name: "error", + }, }, }, }, @@ -163,17 +280,91 @@ func TestParseDirectory(t *testing.T) { Name: "StructPointerInputStructPointerOutput", Inputs: []*Parameter{ { - Name: "in", - Type: "Person", - IsStruct: true, - IsPointer: true, + Name: "in", + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + }, }, }, Outputs: []*Parameter{ { - Type: "Person", - IsPointer: true, - IsStruct: true, + Type: &ParameterType{ + Name: "Person", + IsPointer: true, + IsStruct: true, + }, + }, + }, + }, + { + Name: "MapIntInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + }, + }, + }, + }, + Outputs: []*Parameter{}, + }, + { + Name: "MapIntSliceInt", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + Outputs: []*Parameter{}, + }, + { + Name: "MapIntSliceIntInMapIntSliceIntOut", + Inputs: []*Parameter{ + { + Name: "in", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, + }, + }, + Outputs: []*Parameter{ + { + Name: "out", + Type: &ParameterType{ + Name: "map", + MapKey: &ParameterType{ + Name: "int", + }, + MapValue: &ParameterType{ + Name: "int", + IsSlice: true, + }, + }, }, }, }, @@ -208,7 +399,6 @@ func TestParseDirectory(t *testing.T) { t.Errorf("ParseDirectory() error = %v, wantErr %v", err, tt.wantErr) return } - if diff := cmp.Diff(tt.wantBoundMethods, got.BoundMethods); diff != "" { t.Errorf("ParseDirectory() failed:\n" + diff) } diff --git a/v3/internal/parser/testdata/struct_literal_single/main.go b/v3/internal/parser/testdata/struct_literal_single/main.go index d544638ed..57d5365ee 100644 --- a/v3/internal/parser/testdata/struct_literal_single/main.go +++ b/v3/internal/parser/testdata/struct_literal_single/main.go @@ -9,7 +9,8 @@ import ( ) type Person struct { - Name string + Name string + Parent *Person } // GreetService is great @@ -47,6 +48,24 @@ func (*GreetService) IntPointerInputNamedOutputs(in *int) (output *int, err erro return in, nil } +func (*GreetService) IntInIntOut(in int) int { + return in +} + +func (*GreetService) UIntInUIntOut(in uint) uint { + return in +} +func (*GreetService) Float32InFloat32Out(in float32) float32 { + return in +} +func (*GreetService) Float64InFloat64Out(in float64) float64 { + return in +} + +func (*GreetService) BoolInBoolOut(in bool) bool { + return in +} + func (*GreetService) StructPointerInputErrorOutput(in *Person) error { return nil } @@ -55,6 +74,16 @@ func (*GreetService) StructPointerInputStructPointerOutput(in *Person) *Person { return in } +func (*GreetService) MapIntInt(in map[int]int) { +} + +func (*GreetService) MapIntSliceInt(in map[int][]int) { +} + +func (*GreetService) MapIntSliceIntInMapIntSliceIntOut(in map[int][]int) (out map[int][]int) { + return nil +} + func main() { app := application.New(application.Options{ Bind: []interface{}{