Parse Models

Support recursive fields
Add loads of tests
This commit is contained in:
Lea Anthony
2023-02-20 20:36:58 +11:00
parent 95bb15eef4
commit d0769ecd1c
5 changed files with 447 additions and 851 deletions
+39 -5
View File
@@ -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
+134 -33
View File
@@ -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 ""
}
File diff suppressed because it is too large Load Diff
+244 -54
View File
@@ -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)
}
+30 -1
View File
@@ -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{}{