From 1b1a7ab390aec00007e9cf4a4605483db84b606b Mon Sep 17 00:00:00 2001 From: Pandurang Patil Date: Sat, 16 Sep 2023 13:15:28 +0530 Subject: [PATCH] Refactored code for `mod` file processing as well as `go` file processing (#31) * Refactored code for mod file processing 1. Refactored code and moved mod file processing in separate file along with object oriented implementation * Refactored go file processor 1. Refactored the code for go file processing in object oriented way instead of passing some common variables across the functions. --- goastgen/gofileparser.go | 350 ++++++++++++++++++++++++ goastgen/libgoastgen.go | 420 ----------------------------- goastgen/libgoastgen_array_test.go | 50 ++-- goastgen/libgoastgen_ast_test.go | 9 +- goastgen/libgoastgen_map_test.go | 50 ++-- goastgen/libgoastgen_test.go | 77 +++--- goastgen/modfileparser.go | 62 +++++ main.go | 9 +- 8 files changed, 494 insertions(+), 533 deletions(-) create mode 100644 goastgen/gofileparser.go create mode 100644 goastgen/modfileparser.go diff --git a/goastgen/gofileparser.go b/goastgen/gofileparser.go new file mode 100644 index 0000000..5f5f6ea --- /dev/null +++ b/goastgen/gofileparser.go @@ -0,0 +1,350 @@ +package goastgen + +import ( + "go/ast" + "go/parser" + "go/token" + "log" + "reflect" + "unsafe" +) + +type GoFile struct { + File string + // Last node id reference + lastNodeId int + //fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and File details to the node. + fset *token.FileSet + // We maintain the cache of processed object pointers mapped to their respective node_id + nodeAddressMap map[uintptr]interface{} +} + +//Parse +/* + It will parse the given File and generate AST in JSON format + + Parameters: + File: absolute File path to be parsed + + Returns: + If given File is a valid go code then it will generate AST in JSON format otherwise will return "" string. +*/ +func (goFile *GoFile) Parse() (string, error) { + goFile.fset = token.NewFileSet() + goFile.lastNodeId = 1 + goFile.nodeAddressMap = make(map[uintptr]interface{}) + // NOTE: Haven't explore much of mode parameter. Default value has been passed as 0 + parsedAst, err := parser.ParseFile(goFile.fset, goFile.File, nil, 0) + if err != nil { + log.SetPrefix("[ERROR]") + log.Println("Error while parsing source File -> '", goFile.File, ",") + log.Print(err) + return "", err + } + // We maintain the cache of processed object pointers mapped to their respective node_id + // Last node id reference + result := goFile.serilizeToMap(parsedAst) + return serilizeToJsonStr(result) +} + +// ParseAstFromSource +/* + It will parse given source code and generate AST in JSON format + + Parameters: + filename: Filename used for generating AST metadata + src: string, []byte, or io.Reader - Source code + + Returns: + If given source is valid go source then it will generate AST in JSON format other will return "" string. +*/ +func (goFile *GoFile) ParseAstFromSource(src any) (string, error) { + goFile.fset = token.NewFileSet() + goFile.lastNodeId = 1 + goFile.nodeAddressMap = make(map[uintptr]interface{}) + parsedAst, err := parser.ParseFile(goFile.fset, goFile.File, src, 0) + if err != nil { + // TODO: convert this to just warning error log. + log.SetPrefix("[ERROR]") + log.Println("Error while parsing source from source File -> '", goFile.File, "'") + log.Print(err) + return "", err + } + result := goFile.serilizeToMap(parsedAst) + return serilizeToJsonStr(result) +} + +/* + First step to convert the given object to Map, in order to export into JSON format. + + This function will check if the given passed object is of primitive, struct, map, array or slice (Dynamic array) type + and process object accordingly to convert the same to map[string]interface + + In case the object itself is of primitive data type, it will not convert it to map, rather it will just return the same object as is. + + Parameters: + node: any object + fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and File details to the node. + + Returns: + possible return value types could be primitive type, map (map[string]interface{}) or slice ([]interface{}) + +*/ +func (goFile *GoFile) serilizeToMap(node interface{}) interface{} { + var elementType reflect.Type + var elementValue reflect.Value + var ptrValue reflect.Value + nodeType := reflect.TypeOf(node) + nodeValue := reflect.ValueOf(node) + // If the first object itself is the pointer then get the underlying object 'Value' and process it. + if nodeType.Kind() == reflect.Pointer { + // NOTE: This handles only one level of pointer. At this moment we don't expect to get pointer to pointer. + //This will get 'reflect.Value' object pointed to by this pointer. + elementType = nodeType.Elem() + //This will get 'reflect.Type' object pointed to by this pointer + elementValue = nodeValue.Elem() + ptrValue = nodeValue + } else { + elementType = nodeType + elementValue = nodeValue + } + + // In case the node is pointer, it will check if given Value contains valid pointer address. + if elementValue.IsValid() { + switch elementType.Kind() { + case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return elementValue.Interface() + case reflect.Struct: + return goFile.processStruct(elementValue.Interface(), ptrValue) + case reflect.Map: + return goFile.processMap(elementValue.Interface()) + case reflect.Array, reflect.Slice: + return goFile.processArrayOrSlice(elementValue.Interface()) + default: + log.SetPrefix("[WARNING]") + log.Println(getLogPrefix(), elementType.Kind(), " - not handled") + return elementValue.Interface() + } + } + return nil +} + +/* + This will process object of 'struct' type and convert it into document / map[string]interface{}. + It will process each field of this object, if it contains further child objects, arrays or maps. + Then it will get those respective field objects processed through respective processors. + e.g. if the field object is of type 'struct' then it will call function processStruct recursively + + Parameters: + node: Object of struct + objPtrValue: reflect.Value - As we cannot get the pointer information from reflect.Value object. + If its a pointer that is getting processed, the caller will pass the reflect.Value of pointer. + So that it can be used for checking the cache if the given object pointed by the same pointer is already processed or not. + + Returns: + It will return object of map[string]interface{} by converting all the child fields recursively into map + +*/ +func (goFile *GoFile) processStruct(node interface{}, objPtrValue reflect.Value) interface{} { + objectMap := make(map[string]interface{}) + elementType := reflect.TypeOf(node) + elementValueObj := reflect.ValueOf(node) + + process := true + var objAddress uintptr + // We are checking if the given object is already processed. + // We are doing that by maintaining map of processed object pointers set with node_id. + // If object is already processed then we will not process it again. + // Instead we wil just add its node_id as reference id + + // NOTE: Important point to understand we are not maintaining every object in this cache. + // We are only maintaining those objects which are referenced as a pointer. In that case objPtrValue.Kind() will be of reflect.Pointer type + if objPtrValue.Kind() == reflect.Pointer { + ptr := unsafe.Pointer(objPtrValue.Pointer()) // Get the pointer address as an unsafe.Pointer + objAddress = uintptr(ptr) // Convert unsafe.Pointer to uintptr + refNodeId, ok := goFile.nodeAddressMap[objAddress] + if ok { + process = false + //if the given object is already processed, then we are adding its respective node_id as a reference_id in this node. + objectMap["node_reference_id"] = refNodeId + } + // Reading and setting column no, line no and File details. + if astNode, ok := objPtrValue.Interface().(ast.Node); ok && goFile.fset != nil { + if pos := astNode.Pos(); pos.IsValid() { + position := goFile.fset.Position(pos) + //Add File information only inside ast.File node which is the root node for a File AST. + if elementValueObj.Type().String() == "ast.File" { + objectMap["node_filename"] = position.Filename + } + objectMap["node_line_no"] = position.Line + objectMap["node_col_no"] = position.Column + } + if epos := astNode.End(); epos.IsValid() { + position := goFile.fset.Position(epos) + objectMap["node_line_no_end"] = position.Line + objectMap["node_col_no_end"] = position.Column + } + } + } + + objectMap["node_id"] = goFile.lastNodeId + goFile.lastNodeId++ + objectMap["node_type"] = elementValueObj.Type().String() + + if process { + if objPtrValue.Kind() == reflect.Pointer { + goFile.nodeAddressMap[objAddress] = objectMap["node_id"] + } + // We will iterate through each field process each field according to its reflect.Kind type. + for i := 0; i < elementType.NumField(); i++ { + field := elementType.Field(i) + + value := elementValueObj.Field(i) + fieldKind := value.Type().Kind() + + // If object is defined with field type as interface{} and assigned with pointer value. + // We need to first fetch the element from the interface + if fieldKind == reflect.Interface { + fieldKind = value.Elem().Kind() + value = value.Elem() + } + + var ptrValue reflect.Value + + if fieldKind == reflect.Pointer { + // NOTE: This handles only one level of pointer. At this moment we don't expect to get pointer to pointer. + // This will fetch the reflect.Kind of object pointed to by this field pointer + fieldKind = value.Type().Elem().Kind() + // This will fetch the reflect.Value of object pointed to by this field pointer. + ptrValue = value + // capturing the reflect.Value of the pointer if it's a pointer to be passed to recursive processStruct method. + value = value.Elem() + } + // In case the node is pointer, it will check if given Value contains valid pointer address. + if value.IsValid() { + switch fieldKind { + case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if value.Type().String() == "token.Token" { + objectMap[field.Name] = value.Interface().(token.Token).String() + } else { + objectMap[field.Name] = value.Interface() + } + case reflect.Struct: + objectMap[field.Name] = goFile.processStruct(value.Interface(), ptrValue) + case reflect.Map: + objectMap[field.Name] = goFile.processMap(value.Interface()) + case reflect.Array, reflect.Slice: + objectMap[field.Name] = goFile.processArrayOrSlice(value.Interface()) + default: + log.SetPrefix("[WARNING]") + log.Println(getLogPrefix(), field.Name, "- of Kind ->", fieldKind, "- not handled") + } + } + } + } + return objectMap +} + +/* + This will process the Array or Slice (Dynamic Array). + It will identify the type/reflect.Kind of each array element and process the array element according. + + Parameters: + object: []interface{} - expected to pass object of Array or Slice + + Returns: + It will return []map[string]interface{} +*/ +func (goFile *GoFile) processArrayOrSlice(object interface{}) interface{} { + value := reflect.ValueOf(object) + var nodeList []interface{} + for j := 0; j < value.Len(); j++ { + arrayElementValue := value.Index(j) + elementKind := arrayElementValue.Kind() + // If you create an array interface{} and assign pointer as elements into this array. + // when we try to identify the reflect.Kind of such element it will be of type reflect.Interface. + // In such case we need to call .elem() to fetch the original reflect.Value of the array element. + // Refer test case - TestSimpleInterfaceWithArrayOfPointersType for the same. + if elementKind == reflect.Interface { + arrayElementValue = arrayElementValue.Elem() + elementKind = arrayElementValue.Kind() + } + ptrValue := arrayElementValue + + switch elementKind { + case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + nodeList = append(nodeList, arrayElementValue.Interface()) + case reflect.Struct: + nodeList = append(nodeList, goFile.processStruct(arrayElementValue.Interface(), ptrValue)) + case reflect.Map: + nodeList = append(nodeList, goFile.processMap(arrayElementValue.Interface())) + case reflect.Pointer: + // In case the node is pointer, it will check if given Value contains valid pointer address. + if arrayElementValue.Elem().IsValid() { + arrayElementValuePtrKind := arrayElementValue.Elem().Kind() + switch arrayElementValuePtrKind { + case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + nodeList = append(nodeList, arrayElementValue.Elem().Interface()) + case reflect.Struct: + nodeList = append(nodeList, goFile.processStruct(arrayElementValue.Elem().Interface(), ptrValue)) + case reflect.Map: + nodeList = append(nodeList, goFile.processMap(arrayElementValue.Elem().Interface())) + default: + log.SetPrefix("[WARNING]") + log.Println(getLogPrefix(), arrayElementValuePtrKind, "- not handled for array pointer element") + } + } + default: + log.SetPrefix("[WARNING]") + log.Println(getLogPrefix(), elementKind, "- not handled for array element") + } + } + return nodeList +} + +/* +Process Map type objects. In order to process the contents of the map's value object. +If the value object is of type 'struct' then we are converting it to map[string]interface{} and using it. + +Parameters: + object: expects map[string] any + + +Returns: + It returns and object of map[string]interface{} by converting any 'Struct' type value field to map +*/ +func (goFile *GoFile) processMap(object interface{}) interface{} { + value := reflect.ValueOf(object) + objMap := make(map[string]interface{}) + for _, key := range value.MapKeys() { + objValue := value.MapIndex(key) + + // If the map is created to accept valye of any type i.e. map[string]interface{}. + // Then it's value's reflect.Kind is of type reflect.Interface. + // We need to fetch original objects reflect.Value by calling .Elem() on it. + if objValue.Kind() == reflect.Interface { + objValue = objValue.Elem() + } + + var ptrValue reflect.Value + // Checking the reflect.Kind of value object and if its pointer + // then fetching the reflect.Value of the object pointed to by this pointer + if objValue.Kind() == reflect.Pointer { + objValue = objValue.Elem() + ptrValue = objValue + } + + if objValue.IsValid() { + switch objValue.Kind() { + case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + objMap[key.String()] = objValue.Interface() + case reflect.Struct: + objMap[key.String()] = goFile.processStruct(objValue.Interface(), ptrValue) + default: + log.SetPrefix("[WARNING]") + log.Println(getLogPrefix(), objValue.Kind(), "- not handled") + } + } + } + return objMap +} diff --git a/goastgen/libgoastgen.go b/goastgen/libgoastgen.go index e9ef41f..764de82 100644 --- a/goastgen/libgoastgen.go +++ b/goastgen/libgoastgen.go @@ -2,152 +2,9 @@ package goastgen import ( "encoding/json" - "go/ast" - "go/parser" - "go/token" - "golang.org/x/mod/modfile" - "io/ioutil" "log" - "reflect" - "unsafe" ) -// ParseAstFromSource -/* - It will parse given source code and generate AST in JSON format - - Parameters: - filename: Filename used for generating AST metadata - src: string, []byte, or io.Reader - Source code - - Returns: - If given source is valid go source then it will generate AST in JSON format other will return "" string. -*/ -func ParseAstFromSource(filename string, src any) (string, error) { - fset := token.NewFileSet() - parsedAst, err := parser.ParseFile(fset, filename, src, 0) - if err != nil { - // TODO: convert this to just warning error log. - log.SetPrefix("[ERROR]") - log.Println("Error while parsing source from source file -> '", filename, "'") - log.Print(err) - return "", err - } - // We maintain the cache of processed object pointers mapped to their respective node_id - var nodeAddressMap = make(map[uintptr]interface{}) - // Last node id reference - lastNodeId := 1 - result := serilizeToMap(parsedAst, fset, &lastNodeId, nodeAddressMap) - return serilizeToJsonStr(result) -} - -//ParseAstFromDir -/* - It will parse all the go files in given source folder location and generate AST in JSON format - - Parameters: - file: absolute root directory path of source code - - Returns: - If given directory contains valid go source code then it will generate AST in JSON format otherwise will return "" string. -*/ -func ParseAstFromDir(dir string) (string, error) { - fset := token.NewFileSet() - parsedAst, err := parser.ParseDir(fset, dir, nil, 0) - if err != nil { - // TODO: convert this to just warning error log. - log.SetPrefix("[ERROR]") - log.Println("Error while parsing source from source directory -> '", dir, "'") - log.Print(err) - return "", err - } - // We maintain the cache of processed object pointers mapped to their respective node_id - var nodeAddressMap = make(map[uintptr]interface{}) - // Last node id reference - lastNodeId := 1 - result := serilizeToMap(parsedAst, fset, &lastNodeId, nodeAddressMap) - return serilizeToJsonStr(result) -} - -//ParseAstFromFile -/* - It will parse the given file and generate AST in JSON format - - Parameters: - file: absolute file path to be parsed - - Returns: - If given file is a valid go code then it will generate AST in JSON format otherwise will return "" string. -*/ -func ParseAstFromFile(file string) (string, error) { - fset := token.NewFileSet() - // NOTE: Haven't explore much of mode parameter. Default value has been passed as 0 - parsedAst, err := parser.ParseFile(fset, file, nil, 0) - if err != nil { - log.SetPrefix("[ERROR]") - log.Println("Error while parsing source file -> '", file, ",") - log.Print(err) - return "", err - } - // We maintain the cache of processed object pointers mapped to their respective node_id - var nodeAddressMap = make(map[uintptr]interface{}) - // Last node id reference - lastNodeId := 1 - result := serilizeToMap(parsedAst, fset, &lastNodeId, nodeAddressMap) - return serilizeToJsonStr(result) -} - -// ParseModFromFile -/* - It will parse the .mod file and generate module and dependency information in JSON format - - Parameters: - file: absolute file path to be parsed - - Returns: - If given file is a valid .mod file then it will generate the module and dependency information in JSON format -*/ -func ParseModFromFile(file string) (string, error) { - objMap := make(map[string]interface{}) - contents, err := ioutil.ReadFile(file) - if err != nil { - log.SetPrefix("[ERROR]") - log.Printf("Error while processing '%s' \n", file) - log.Println(err) - return "", err - } - modFile, err := modfile.Parse(file, contents, nil) - if err != nil { - log.SetPrefix("[ERROR]") - log.Printf("Error while processing '%s' \n", file) - log.Println(err) - return "", err - } - objMap["node_filename"] = file - module := make(map[string]interface{}) - module["Name"] = modFile.Module.Mod.Path - module["node_line_no"] = modFile.Module.Syntax.Start.Line - module["node_col_no"] = modFile.Module.Syntax.Start.LineRune - module["node_line_no_end"] = modFile.Module.Syntax.End.Line - module["node_col_no_end"] = modFile.Module.Syntax.End.LineRune - module["node_type"] = "mod.Module" - objMap["Module"] = module - dependencies := []interface{}{} - for _, req := range modFile.Require { - node := make(map[string]interface{}) - node["Module"] = req.Mod.Path - node["Version"] = req.Mod.Version - node["node_line_no"] = req.Syntax.Start.Line - node["node_col_no"] = req.Syntax.Start.LineRune - node["node_line_no_end"] = req.Syntax.End.Line - node["node_col_no_end"] = req.Syntax.End.LineRune - node["node_type"] = "mod.Dependency" - dependencies = append(dependencies, node) - } - objMap["dependencies"] = dependencies - return serilizeToJsonStr(objMap) -} - /* Independent function which handles serialisation of map[string]interface{} in to JSON @@ -167,280 +24,3 @@ func serilizeToJsonStr(objectMap interface{}) (string, error) { } return string(jsonStr), nil } - -/* -Process Map type objects. In order to process the contents of the map's value object. -If the value object is of type 'struct' then we are converting it to map[string]interface{} and using it. - -Parameters: - object: expects map[string] any - fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and file details to the node. - -Returns: - It returns and object of map[string]interface{} by converting any 'Struct' type value field to map -*/ -func processMap(object interface{}, fset *token.FileSet, lastNodeId *int, nodeAddressMap map[uintptr]interface{}) interface{} { - value := reflect.ValueOf(object) - objMap := make(map[string]interface{}) - for _, key := range value.MapKeys() { - objValue := value.MapIndex(key) - - // If the map is created to accept valye of any type i.e. map[string]interface{}. - // Then it's value's reflect.Kind is of type reflect.Interface. - // We need to fetch original objects reflect.Value by calling .Elem() on it. - if objValue.Kind() == reflect.Interface { - objValue = objValue.Elem() - } - - var ptrValue reflect.Value - // Checking the reflect.Kind of value object and if its pointer - // then fetching the reflect.Value of the object pointed to by this pointer - if objValue.Kind() == reflect.Pointer { - objValue = objValue.Elem() - ptrValue = objValue - } - - if objValue.IsValid() { - switch objValue.Kind() { - case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - objMap[key.String()] = objValue.Interface() - case reflect.Struct: - objMap[key.String()] = processStruct(objValue.Interface(), ptrValue, fset, lastNodeId, nodeAddressMap) - default: - log.SetPrefix("[WARNING]") - log.Println(getLogPrefix(), objValue.Kind(), "- not handled") - } - } - } - return objMap -} - -/* - This will process the Array or Slice (Dynamic Array). - It will identify the type/reflect.Kind of each array element and process the array element according. - - Parameters: - object: []interface{} - expected to pass object of Array or Slice - fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and file details to the node. - - Returns: - It will return []map[string]interface{} -*/ -func processArrayOrSlice(object interface{}, fset *token.FileSet, lastNodeId *int, nodeAddressMap map[uintptr]interface{}) interface{} { - value := reflect.ValueOf(object) - var nodeList []interface{} - for j := 0; j < value.Len(); j++ { - arrayElementValue := value.Index(j) - elementKind := arrayElementValue.Kind() - // If you create an array interface{} and assign pointer as elements into this array. - // when we try to identify the reflect.Kind of such element it will be of type reflect.Interface. - // In such case we need to call .elem() to fetch the original reflect.Value of the array element. - // Refer test case - TestSimpleInterfaceWithArrayOfPointersType for the same. - if elementKind == reflect.Interface { - arrayElementValue = arrayElementValue.Elem() - elementKind = arrayElementValue.Kind() - } - ptrValue := arrayElementValue - - switch elementKind { - case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - nodeList = append(nodeList, arrayElementValue.Interface()) - case reflect.Struct: - nodeList = append(nodeList, processStruct(arrayElementValue.Interface(), ptrValue, fset, lastNodeId, nodeAddressMap)) - case reflect.Map: - nodeList = append(nodeList, processMap(arrayElementValue.Interface(), fset, lastNodeId, nodeAddressMap)) - case reflect.Pointer: - // In case the node is pointer, it will check if given Value contains valid pointer address. - if arrayElementValue.Elem().IsValid() { - arrayElementValuePtrKind := arrayElementValue.Elem().Kind() - switch arrayElementValuePtrKind { - case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - nodeList = append(nodeList, arrayElementValue.Elem().Interface()) - case reflect.Struct: - nodeList = append(nodeList, processStruct(arrayElementValue.Elem().Interface(), ptrValue, fset, lastNodeId, nodeAddressMap)) - case reflect.Map: - nodeList = append(nodeList, processMap(arrayElementValue.Elem().Interface(), fset, lastNodeId, nodeAddressMap)) - default: - log.SetPrefix("[WARNING]") - log.Println(getLogPrefix(), arrayElementValuePtrKind, "- not handled for array pointer element") - } - } - default: - log.SetPrefix("[WARNING]") - log.Println(getLogPrefix(), elementKind, "- not handled for array element") - } - } - return nodeList -} - -/* - This will process object of 'struct' type and convert it into document / map[string]interface{}. - It will process each field of this object, if it contains further child objects, arrays or maps. - Then it will get those respective field objects processed through respective processors. - e.g. if the field object is of type 'struct' then it will call function processStruct recursively - - Parameters: - node: Object of struct - objPtrValue: reflect.Value - As we cannot get the pointer information from reflect.Value object. - If its a pointer that is getting processed, the caller will pass the reflect.Value of pointer. - So that it can be used for checking the cache if the given object pointed by the same pointer is already processed or not. - fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and file details to the node. - - Returns: - It will return object of map[string]interface{} by converting all the child fields recursively into map - -*/ -func processStruct(node interface{}, objPtrValue reflect.Value, fset *token.FileSet, lastNodeId *int, nodeAddressMap map[uintptr]interface{}) interface{} { - objectMap := make(map[string]interface{}) - elementType := reflect.TypeOf(node) - elementValueObj := reflect.ValueOf(node) - - process := true - var objAddress uintptr - // We are checking if the given object is already processed. - // We are doing that by maintaining map of processed object pointers set with node_id. - // If object is already processed then we will not process it again. - // Instead we wil just add its node_id as reference id - - // NOTE: Important point to understand we are not maintaining every object in this cache. - // We are only maintaining those objects which are referenced as a pointer. In that case objPtrValue.Kind() will be of reflect.Pointer type - if objPtrValue.Kind() == reflect.Pointer { - ptr := unsafe.Pointer(objPtrValue.Pointer()) // Get the pointer address as an unsafe.Pointer - objAddress = uintptr(ptr) // Convert unsafe.Pointer to uintptr - refNodeId, ok := nodeAddressMap[objAddress] - if ok { - process = false - //if the given object is already processed, then we are adding its respective node_id as a reference_id in this node. - objectMap["node_reference_id"] = refNodeId - } - // Reading and setting column no, line no and file details. - if astNode, ok := objPtrValue.Interface().(ast.Node); ok && fset != nil { - if pos := astNode.Pos(); pos.IsValid() { - position := fset.Position(pos) - //Add file information only inside ast.File node which is the root node for a file AST. - if elementValueObj.Type().String() == "ast.File" { - objectMap["node_filename"] = position.Filename - } - objectMap["node_line_no"] = position.Line - objectMap["node_col_no"] = position.Column - } - if epos := astNode.End(); epos.IsValid() { - position := fset.Position(epos) - objectMap["node_line_no_end"] = position.Line - objectMap["node_col_no_end"] = position.Column - } - } - } - - objectMap["node_id"] = *lastNodeId - *lastNodeId++ - objectMap["node_type"] = elementValueObj.Type().String() - - if process { - if objPtrValue.Kind() == reflect.Pointer { - nodeAddressMap[objAddress] = objectMap["node_id"] - } - // We will iterate through each field process each field according to its reflect.Kind type. - for i := 0; i < elementType.NumField(); i++ { - field := elementType.Field(i) - - value := elementValueObj.Field(i) - fieldKind := value.Type().Kind() - - // If object is defined with field type as interface{} and assigned with pointer value. - // We need to first fetch the element from the interface - if fieldKind == reflect.Interface { - fieldKind = value.Elem().Kind() - value = value.Elem() - } - - var ptrValue reflect.Value - - if fieldKind == reflect.Pointer { - // NOTE: This handles only one level of pointer. At this moment we don't expect to get pointer to pointer. - // This will fetch the reflect.Kind of object pointed to by this field pointer - fieldKind = value.Type().Elem().Kind() - // This will fetch the reflect.Value of object pointed to by this field pointer. - ptrValue = value - // capturing the reflect.Value of the pointer if it's a pointer to be passed to recursive processStruct method. - value = value.Elem() - } - // In case the node is pointer, it will check if given Value contains valid pointer address. - if value.IsValid() { - switch fieldKind { - case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if value.Type().String() == "token.Token" { - objectMap[field.Name] = value.Interface().(token.Token).String() - } else { - objectMap[field.Name] = value.Interface() - } - case reflect.Struct: - objectMap[field.Name] = processStruct(value.Interface(), ptrValue, fset, lastNodeId, nodeAddressMap) - case reflect.Map: - objectMap[field.Name] = processMap(value.Interface(), fset, lastNodeId, nodeAddressMap) - case reflect.Array, reflect.Slice: - objectMap[field.Name] = processArrayOrSlice(value.Interface(), fset, lastNodeId, nodeAddressMap) - default: - log.SetPrefix("[WARNING]") - log.Println(getLogPrefix(), field.Name, "- of Kind ->", fieldKind, "- not handled") - } - } - } - } - return objectMap -} - -/* - First step to convert the given object to Map, in order to export into JSON format. - - This function will check if the given passed object is of primitive, struct, map, array or slice (Dynamic array) type - and process object accordingly to convert the same to map[string]interface - - In case the object itself is of primitive data type, it will not convert it to map, rather it will just return the same object as is. - - Parameters: - node: any object - fset: *token.FileSet - As this library is primarily designed to generate AST JSON. This parameter facilitate adding line, column no and file details to the node. - - Returns: - possible return value types could be primitive type, map (map[string]interface{}) or slice ([]interface{}) - -*/ -func serilizeToMap(node interface{}, fset *token.FileSet, lastNodeId *int, nodeAddressMap map[uintptr]interface{}) interface{} { - var elementType reflect.Type - var elementValue reflect.Value - var ptrValue reflect.Value - nodeType := reflect.TypeOf(node) - nodeValue := reflect.ValueOf(node) - // If the first object itself is the pointer then get the underlying object 'Value' and process it. - if nodeType.Kind() == reflect.Pointer { - // NOTE: This handles only one level of pointer. At this moment we don't expect to get pointer to pointer. - //This will get 'reflect.Value' object pointed to by this pointer. - elementType = nodeType.Elem() - //This will get 'reflect.Type' object pointed to by this pointer - elementValue = nodeValue.Elem() - ptrValue = nodeValue - } else { - elementType = nodeType - elementValue = nodeValue - } - - // In case the node is pointer, it will check if given Value contains valid pointer address. - if elementValue.IsValid() { - switch elementType.Kind() { - case reflect.String, reflect.Int, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return elementValue.Interface() - case reflect.Struct: - return processStruct(elementValue.Interface(), ptrValue, fset, lastNodeId, nodeAddressMap) - case reflect.Map: - return processMap(elementValue.Interface(), fset, lastNodeId, nodeAddressMap) - case reflect.Array, reflect.Slice: - return processArrayOrSlice(elementValue.Interface(), fset, lastNodeId, nodeAddressMap) - default: - log.SetPrefix("[WARNING]") - log.Println(getLogPrefix(), elementType.Kind(), " - not handled") - return elementValue.Interface() - } - } - return nil -} diff --git a/goastgen/libgoastgen_array_test.go b/goastgen/libgoastgen_array_test.go index 4ee2023..d3b5cee 100644 --- a/goastgen/libgoastgen_array_test.go +++ b/goastgen/libgoastgen_array_test.go @@ -6,49 +6,45 @@ import ( ) func TestArrayWithnillPointerCheck(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} var nilStr *string var nilObj *Phone var nilMap *map[string]Phone arrayWithnil := [4]interface{}{"valid string", nilStr, nilObj, nilMap} - result := processArrayOrSlice(arrayWithnil, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(arrayWithnil) expectedResult := []interface{}{"valid string"} assert.Equal(t, expectedResult, result, "It should process valid values of the array successfully") } func TestSimpleInterfaceWithArray(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} arrayType := [2]interface{}{"first", "second"} - result := processArrayOrSlice(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(arrayType) expectedResult := []interface{}{"first", "second"} assert.Equal(t, expectedResult, result, "Array of interface containing string pointers should match with expected results") } func TestSimpleInterfaceWithArrayOfPointersType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} first := "first" second := "second" arrayType := [2]interface{}{&first, &second} - result := processArrayOrSlice(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(arrayType) expectedResult := []interface{}{"first", "second"} assert.Equal(t, expectedResult, result, "Array of interface containing string pointers should match with expected results") } func TestObjectInterfaceWithArrayOfPointers(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone1 := Phone{PhoneNo: "1234567890", Type: "Home"} phone2 := Phone{PhoneNo: "0987654321", Type: "Office"} arrayType := [2]interface{}{&phone1, &phone2} - result := processArrayOrSlice(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(arrayType) firstPhoneItem := make(map[string]interface{}) firstPhoneItem["PhoneNo"] = "1234567890" firstPhoneItem["Type"] = "Home" @@ -64,13 +60,12 @@ func TestObjectInterfaceWithArrayOfPointers(t *testing.T) { } func TestSliceObjctPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone1 := Phone{PhoneNo: "1234567890", Type: "Home"} phone2 := Phone{PhoneNo: "0987654321", Type: "Office"} objArrayType := SliceObjPtrType{Id: 20, PhoneList: []*Phone{&phone1, &phone2}} - result := serilizeToMap(objArrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(objArrayType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 20 expectedResult["node_type"] = "goastgen.SliceObjPtrType" @@ -91,14 +86,13 @@ func TestSliceObjctPtrType(t *testing.T) { } func TestArrayPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} firstStr := "First" secondStr := "Second" thirdStr := "Third" arrayType := ArrayPtrType{Id: 10, NameList: [3]*string{&firstStr, &secondStr, &thirdStr}} - result := serilizeToMap(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(arrayType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 10 expectedResult["node_type"] = "goastgen.ArrayPtrType" @@ -109,11 +103,10 @@ func TestArrayPtrType(t *testing.T) { } func TestObjectSliceType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} objArrayType := ObjectSliceType{Id: 20, PhoneList: []Phone{{PhoneNo: "1234567890", Type: "Home"}, {PhoneNo: "0987654321", Type: "Office"}}} - result := serilizeToMap(objArrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(objArrayType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 20 expectedResult["node_type"] = "goastgen.ObjectSliceType" @@ -134,11 +127,10 @@ func TestObjectSliceType(t *testing.T) { } func TestArrayType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} arrayType := ArrayType{Id: 10, NameList: [3]string{"First", "Second", "Third"}} - result := serilizeToMap(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(arrayType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 10 expectedResult["NameList"] = []interface{}{"First", "Second", "Third"} @@ -148,10 +140,9 @@ func TestArrayType(t *testing.T) { } func TestSliceType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} arrayType := SliceType{Id: 10, NameList: []string{"First", "Second"}} - result := serilizeToMap(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(arrayType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 10 expectedResult["NameList"] = []interface{}{"First", "Second"} @@ -161,14 +152,13 @@ func TestSliceType(t *testing.T) { } func TestSimpleArrayType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone1 := Phone{PhoneNo: "1234567890", Type: "Home"} phone2 := Phone{PhoneNo: "0987654321", Type: "Office"} simplePtrStr := "Simple PTR String" arrayType := []interface{}{&phone1, phone2, "Simple String", 90, &simplePtrStr} - result := serilizeToMap(arrayType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(arrayType) firstPhone := make(map[string]interface{}) firstPhone["PhoneNo"] = "1234567890" diff --git a/goastgen/libgoastgen_ast_test.go b/goastgen/libgoastgen_ast_test.go index 9a3da63..a546485 100644 --- a/goastgen/libgoastgen_ast_test.go +++ b/goastgen/libgoastgen_ast_test.go @@ -13,12 +13,11 @@ type RecursivePtrType struct { } func TestRecursivePointerCheck(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} recursivePtrType := RecursivePtrType{Id: 10, Name: "Gajraj"} recursivePtrType.NodePtr = &recursivePtrType - result := serilizeToMap(&recursivePtrType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(&recursivePtrType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 10 expectedResult["Name"] = "Gajraj" @@ -39,8 +38,8 @@ func TestFirst(t *testing.T) { "func main() {\n" + "fmt.Println(\"Hello World\")\n" + "}" - - result, _ := ParseAstFromSource("helloworld.go", code) + var goFile = GoFile{File: "helloworld.go"} + result, _ := goFile.ParseAstFromSource(code) fmt.Println(result) } diff --git a/goastgen/libgoastgen_map_test.go b/goastgen/libgoastgen_map_test.go index f73fb6a..239ea51 100644 --- a/goastgen/libgoastgen_map_test.go +++ b/goastgen/libgoastgen_map_test.go @@ -6,8 +6,7 @@ import ( ) func TestMapWithNilPointerCheck(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} var nilStr *string var nilObj *Phone @@ -16,7 +15,7 @@ func TestMapWithNilPointerCheck(t *testing.T) { mapType["second"] = nilStr mapType["third"] = nilObj - result := processMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.processMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["first"] = "first value" @@ -26,8 +25,7 @@ func TestMapWithNilPointerCheck(t *testing.T) { } func TestArrayOfPointerOfMapOfObjectPointerType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} first := Phone{PhoneNo: "1234567890", Type: "Home"} second := Phone{PhoneNo: "0987654321", Type: "Office"} @@ -40,7 +38,7 @@ func TestArrayOfPointerOfMapOfObjectPointerType(t *testing.T) { secondMap["smfirst"] = &third secondMap["smsecond"] = &forth array := [2]*map[string]*Phone{&firstMap, &secondMap} - result := processArrayOrSlice(array, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(array) firstPhone := make(map[string]interface{}) firstPhone["PhoneNo"] = "1234567890" firstPhone["Type"] = "Home" @@ -73,8 +71,7 @@ func TestArrayOfPointerOfMapOfObjectPointerType(t *testing.T) { } func TestArrayOfPointerOfMapOfPrimitivesType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} firstMap := make(map[string]string) firstMap["fmfirst"] = "fmfirstvalue" @@ -83,7 +80,7 @@ func TestArrayOfPointerOfMapOfPrimitivesType(t *testing.T) { secondMap["smfirst"] = "smfirstvalue" secondMap["smsecond"] = "smsecondvalue" array := [2]*map[string]string{&firstMap, &secondMap} - result := processArrayOrSlice(array, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(array) firstExpectedMap := make(map[string]interface{}) firstExpectedMap["fmfirst"] = "fmfirstvalue" @@ -97,8 +94,7 @@ func TestArrayOfPointerOfMapOfPrimitivesType(t *testing.T) { } func TestArrayOfMapOfPrimitivesType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} firstMap := make(map[string]string) firstMap["fmfirst"] = "fmfirstvalue" @@ -107,7 +103,7 @@ func TestArrayOfMapOfPrimitivesType(t *testing.T) { secondMap["smfirst"] = "smfirstvalue" secondMap["smsecond"] = "smsecondvalue" array := [2]map[string]string{firstMap, secondMap} - result := processArrayOrSlice(array, nil, &lastNodeId, nodeAddressMap) + result := goFile.processArrayOrSlice(array) firstExpectedMap := make(map[string]interface{}) firstExpectedMap["fmfirst"] = "fmfirstvalue" @@ -122,8 +118,7 @@ func TestArrayOfMapOfPrimitivesType(t *testing.T) { } func TestMapObjPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} first := Phone{PhoneNo: "1234567890", Type: "Home"} second := Phone{PhoneNo: "0987654321", Type: "Office"} @@ -132,7 +127,7 @@ func TestMapObjPtrType(t *testing.T) { phones["second"] = &second mapType := MapObjPtrType{Id: 90, Phones: phones} - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 90 expectedResult["node_type"] = "goastgen.MapObjPtrType" @@ -157,8 +152,7 @@ func TestMapObjPtrType(t *testing.T) { } func TestMapStrPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} first := "firstvalue" second := "secondvalue" @@ -166,7 +160,7 @@ func TestMapStrPtrType(t *testing.T) { names["firstname"] = &first names["secondname"] = &second mapType := MapStrPtrType{Id: 30, Names: names} - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 30 expectedResult["node_type"] = "goastgen.MapStrPtrType" @@ -180,15 +174,14 @@ func TestMapStrPtrType(t *testing.T) { } func TestMapObjType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phones := make(map[string]Phone) phones["first"] = Phone{PhoneNo: "1234567890", Type: "Home"} phones["second"] = Phone{PhoneNo: "0987654321", Type: "Office"} mapType := MapObjType{Id: 90, Phones: phones} - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 90 expectedResult["node_type"] = "goastgen.MapObjType" @@ -212,14 +205,13 @@ func TestMapObjType(t *testing.T) { } func TestMapIntType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} names := make(map[string]int) names["firstname"] = 1000 names["secondname"] = 2000 mapType := MapIntType{Id: 30, Names: names} - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 30 expectedResult["node_type"] = "goastgen.MapIntType" @@ -233,14 +225,13 @@ func TestMapIntType(t *testing.T) { } func TestMapType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} names := make(map[string]string) names["firstname"] = "firstvalue" names["secondname"] = "secondvalue" mapType := MapType{Id: 30, Names: names} - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 30 expectedResult["node_type"] = "goastgen.MapType" @@ -253,8 +244,7 @@ func TestMapType(t *testing.T) { } func TestSimpleMapType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone1 := Phone{PhoneNo: "1234567890", Type: "Home"} phone2 := Phone{PhoneNo: "0987654321", Type: "Office"} @@ -263,7 +253,7 @@ func TestSimpleMapType(t *testing.T) { mapType["first"] = &phone1 mapType["second"] = &phone2 - result := serilizeToMap(mapType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(mapType) expectedResult := make(map[string]interface{}) firstPhone := make(map[string]interface{}) firstPhone["PhoneNo"] = "1234567890" diff --git a/goastgen/libgoastgen_test.go b/goastgen/libgoastgen_test.go index 2c2313d..ebad7b6 100644 --- a/goastgen/libgoastgen_test.go +++ b/goastgen/libgoastgen_test.go @@ -76,11 +76,10 @@ type InterfaceStrObjPtrType struct { } func TestInterfaceObjPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone := Phone{PhoneNo: "1234567890", Type: "Home"} interfaceObjPtrType := InterfaceStrObjPtrType{Id: 200, Phone: &phone} - result := serilizeToMap(interfaceObjPtrType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(interfaceObjPtrType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 200 expectedResult["node_type"] = "goastgen.InterfaceStrObjPtrType" @@ -96,11 +95,10 @@ func TestInterfaceObjPtrType(t *testing.T) { } func TestInterfaceStrPtrType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} sampleStr := "Sample" interfaceStrPtrType := InterfaceStrObjPtrType{Id: 100, Name: &sampleStr} - result := serilizeToMap(interfaceStrPtrType, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(interfaceStrPtrType) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 100 expectedResult["Name"] = "Sample" @@ -110,53 +108,49 @@ func TestInterfaceStrPtrType(t *testing.T) { } func TestObjectWithNullValueCheck(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} type SimpleObj struct { Id int Name *string } simpleObj := SimpleObj{Id: 10} - result := serilizeToMap(simpleObj, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(simpleObj) expectedResult := make(map[string]interface{}) expectedResult["Id"] = 10 expectedResult["node_type"] = "goastgen.SimpleObj" expectedResult["node_id"] = 1 assert.Equal(t, expectedResult, result, "It should not process those fields which contains nil pointer, rest of the fields should be processed") - lastNodeId = 1 - nodeAddressMap = make(map[uintptr]interface{}) + goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} type SimpleObjObj struct { Id int Phone *Phone } simpleObjObj := SimpleObjObj{Id: 20} - result = serilizeToMap(simpleObjObj, nil, &lastNodeId, nodeAddressMap) + result = goFile.serilizeToMap(simpleObjObj) expectedResult = make(map[string]interface{}) expectedResult["Id"] = 20 expectedResult["node_type"] = "goastgen.SimpleObjObj" expectedResult["node_id"] = 1 assert.Equal(t, expectedResult, result, "It should not process those fields which contains nil pointer, rest of the fields should be processed") - lastNodeId = 1 - nodeAddressMap = make(map[uintptr]interface{}) + goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} type SimpleObjMap struct { Id int Document *map[string]interface{} } simpleObjMap := SimpleObjObj{Id: 30} - result = serilizeToMap(simpleObjMap, nil, &lastNodeId, nodeAddressMap) + result = goFile.serilizeToMap(simpleObjMap) expectedResult = make(map[string]interface{}) expectedResult["Id"] = 30 expectedResult["node_type"] = "goastgen.SimpleObjObj" expectedResult["node_id"] = 1 assert.Equal(t, expectedResult, result, "It should not process those fields which contains nil pointer, rest of the fields should be processed") - lastNodeId = 1 - nodeAddressMap = make(map[uintptr]interface{}) + goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} type SimpleObjArray struct { Id int Array *[2]string @@ -164,7 +158,7 @@ func TestObjectWithNullValueCheck(t *testing.T) { } simpleObjArray := SimpleObjArray{Id: 40} - result = serilizeToMap(simpleObjArray, nil, &lastNodeId, nodeAddressMap) + result = goFile.serilizeToMap(simpleObjArray) expectedResult = make(map[string]interface{}) expectedResult["Id"] = 40 expectedResult["node_type"] = "goastgen.SimpleObjArray" @@ -174,20 +168,18 @@ func TestObjectWithNullValueCheck(t *testing.T) { } func TestSimpleTypeWithNullValue(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} address := Address{Addone: "First line address"} - result := serilizeToMap(address, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(address) expectedResult := make(map[string]interface{}) expectedResult["Addone"] = "First line address" expectedResult["node_type"] = "goastgen.Address" expectedResult["node_id"] = 1 assert.Equal(t, expectedResult, result, "Simple type result Map should match with expected result Map") - lastNodeId = 1 - nodeAddressMap = make(map[uintptr]interface{}) + goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone := Phone{PhoneNo: "1234567890"} - result = serilizeToMap(phone, nil, &lastNodeId, nodeAddressMap) + result = goFile.serilizeToMap(phone) expectedResult = make(map[string]interface{}) expectedResult["PhoneNo"] = "1234567890" expectedResult["Type"] = "" @@ -197,10 +189,9 @@ func TestSimpleTypeWithNullValue(t *testing.T) { } func TestSimpleType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} phone := Phone{PhoneNo: "1234567890", Type: "Home"} - result := serilizeToMap(phone, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(phone) expectedResult := make(map[string]interface{}) expectedResult["PhoneNo"] = "1234567890" expectedResult["Type"] = "Home" @@ -210,12 +201,11 @@ func TestSimpleType(t *testing.T) { } func TestSimplePointerType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} addtwo := "Second line address" var p *Address p = &Address{Addone: "First line address", Addtwo: &addtwo} - result := serilizeToMap(p, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(p) expectedResult := make(map[string]interface{}) expectedResult["Addone"] = "First line address" expectedResult["Addtwo"] = "Second line address" @@ -226,15 +216,14 @@ func TestSimplePointerType(t *testing.T) { } func TestSecondLevelType(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} addtwo := "Second line address" var a *Address a = &Address{Addone: "First line address", Addtwo: &addtwo} var p *Person p = &Person{Name: "Sample Name", Address: a} - result := serilizeToMap(p, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(p) expectedResult := make(map[string]interface{}) expectedResult["Name"] = "Sample Name" expectedResult["node_type"] = "goastgen.Person" @@ -250,44 +239,42 @@ func TestSecondLevelType(t *testing.T) { } func TestSimplePrimitive(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) - result := serilizeToMap("Hello", nil, &lastNodeId, nodeAddressMap) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} + result := goFile.serilizeToMap("Hello") assert.Equal(t, "Hello", result, "Simple string test should return same value") message := "Hello another message" - result = serilizeToMap(&message, nil, &lastNodeId, nodeAddressMap) + result = goFile.serilizeToMap(&message) assert.Equal(t, "Hello another message", result, "Simple string pointer test should return same value string") } func TestSimpleNullCheck(t *testing.T) { - lastNodeId := 1 - var nodeAddressMap = make(map[uintptr]interface{}) + var goFile = GoFile{File: "", lastNodeId: 1, nodeAddressMap: make(map[uintptr]interface{})} var emptyStr string - result := serilizeToMap(emptyStr, nil, &lastNodeId, nodeAddressMap) + result := goFile.serilizeToMap(emptyStr) assert.Equal(t, "", result, "result should be empty string") var nilValue *string = nil - nilResult := serilizeToMap(nilValue, nil, &lastNodeId, nodeAddressMap) + nilResult := goFile.serilizeToMap(nilValue) assert.Nil(t, nilResult, "Null value should return null") var nillObj *Phone - nilResult = serilizeToMap(nillObj, nil, &lastNodeId, nodeAddressMap) + nilResult = goFile.serilizeToMap(nillObj) assert.Nil(t, nilResult, "Null object should return null") var nillMap *map[string]interface{} - nilResult = serilizeToMap(nillMap, nil, &lastNodeId, nodeAddressMap) + nilResult = goFile.serilizeToMap(nillMap) assert.Nil(t, nilResult, "Null map should return null") var nilSlice *[]string - nilResult = serilizeToMap(nilSlice, nil, &lastNodeId, nodeAddressMap) + nilResult = goFile.serilizeToMap(nilSlice) assert.Nil(t, nilResult, "Null Slice should return null") var nilArray *[2]string - nilResult = serilizeToMap(nilArray, nil, &lastNodeId, nodeAddressMap) + nilResult = goFile.serilizeToMap(nilArray) assert.Nil(t, nilResult, "Null Array should return null") } diff --git a/goastgen/modfileparser.go b/goastgen/modfileparser.go new file mode 100644 index 0000000..e4a7cfa --- /dev/null +++ b/goastgen/modfileparser.go @@ -0,0 +1,62 @@ +package goastgen + +import ( + "golang.org/x/mod/modfile" + "io/ioutil" + "log" +) + +type ModFile struct { + File string +} + +//Parse +/* + It will parse the .mod File and generate module and dependency information in JSON format + + Parameters: + File: absolute File path to be parsed + + Returns: + If given File is a valid .mod File then it will generate the module and dependency information in JSON format +*/ +func (mod *ModFile) Parse() (string, error) { + objMap := make(map[string]interface{}) + contents, err := ioutil.ReadFile(mod.File) + if err != nil { + log.SetPrefix("[ERROR]") + log.Printf("Error while processing '%s' \n", mod.File) + log.Println(err) + return "", err + } + modFile, err := modfile.Parse(mod.File, contents, nil) + if err != nil { + log.SetPrefix("[ERROR]") + log.Printf("Error while processing '%s' \n", mod.File) + log.Println(err) + return "", err + } + objMap["node_filename"] = mod.File + module := make(map[string]interface{}) + module["Name"] = modFile.Module.Mod.Path + module["node_line_no"] = modFile.Module.Syntax.Start.Line + module["node_col_no"] = modFile.Module.Syntax.Start.LineRune + module["node_line_no_end"] = modFile.Module.Syntax.End.Line + module["node_col_no_end"] = modFile.Module.Syntax.End.LineRune + module["node_type"] = "mod.Module" + objMap["Module"] = module + dependencies := []interface{}{} + for _, req := range modFile.Require { + node := make(map[string]interface{}) + node["Module"] = req.Mod.Path + node["Version"] = req.Mod.Version + node["node_line_no"] = req.Syntax.Start.Line + node["node_col_no"] = req.Syntax.Start.LineRune + node["node_line_no_end"] = req.Syntax.End.Line + node["node_col_no_end"] = req.Syntax.End.LineRune + node["node_type"] = "mod.Dependency" + dependencies = append(dependencies, node) + } + objMap["dependencies"] = dependencies + return serilizeToJsonStr(objMap) +} diff --git a/main.go b/main.go index 28a62e4..f793c99 100644 --- a/main.go +++ b/main.go @@ -34,9 +34,11 @@ func processFile(out string, inputPath string, path string, info os.FileInfo, re outFile = filepath.Join(out, strings.ReplaceAll(directory, inputPath, ""), info.Name()+".json") } if strings.HasSuffix(info.Name(), ".go") { - jsonResult, err = goastgen.ParseAstFromFile(path) + goFile := goastgen.GoFile{File: path} + jsonResult, err = goFile.Parse() } else if strings.HasSuffix(info.Name(), ".mod") { - jsonResult, err = goastgen.ParseModFromFile(path) + var modParser = goastgen.ModFile{File: path} + jsonResult, err = modParser.Parse() } if err != nil { fmt.Printf("Failed to generate AST for %s \n", path) @@ -67,7 +69,8 @@ func processRequest(out string, inputPath string, excludeFiles string) { } else { outFile = filepath.Join(out, fileInfo.Name()+".json") } - jsonResult, perr := goastgen.ParseAstFromFile(inputPath) + goFile := goastgen.GoFile{File: inputPath} + jsonResult, perr := goFile.Parse() if perr != nil { fmt.Printf("Failed to generate AST for %s\n", inputPath) return