github.com/viant/toolbox@v0.34.5/fileset_info.go (about)

     1  package toolbox
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/parser"
     7  	"go/token"
     8  	"go/types"
     9  	"io/ioutil"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"strings"
    14  )
    15  
    16  //FieldInfo represents a filed info
    17  type FieldInfo struct {
    18  	Name               string
    19  	TypeName           string
    20  	ComponentType      string
    21  	IsPointerComponent bool
    22  	KeyTypeName        string
    23  	ValueTypeName      string
    24  	TypePackage        string
    25  	IsAnonymous        bool
    26  	IsMap              bool
    27  	IsChannel          bool
    28  	IsSlice            bool
    29  	IsPointer          bool
    30  	Tag                string
    31  	Comment            string
    32  	IsVariant          bool
    33  }
    34  
    35  //NewFunctionInfoFromField creates a new function info.
    36  func NewFunctionInfoFromField(field *ast.Field, owner *FileInfo) *FunctionInfo {
    37  	result := &FunctionInfo{
    38  		Name:            "",
    39  		ParameterFields: make([]*FieldInfo, 0),
    40  		ResultsFields:   make([]*FieldInfo, 0),
    41  	}
    42  	if len(field.Names) > 0 {
    43  		result.Name = field.Names[0].Name
    44  	}
    45  
    46  	if funcType, ok := field.Type.(*ast.FuncType); ok {
    47  		if funcType.Params != nil && len(funcType.Params.List) > 0 {
    48  			result.ParameterFields = toFieldInfoSlice(funcType.Params)
    49  		}
    50  		if funcType.Results != nil && len(funcType.Results.List) > 0 {
    51  			result.ResultsFields = toFieldInfoSlice(funcType.Results)
    52  		}
    53  		var names = make(map[string]bool)
    54  		for _, param := range result.ParameterFields {
    55  			if strings.Contains(strings.ToLower(param.TypeName), strings.ToLower(param.Name)) {
    56  				name := matchLastNameSegment(param.TypeName)
    57  				if _, has := names[name]; has {
    58  					continue
    59  				}
    60  				names[name] = true
    61  				param.Name = name
    62  			}
    63  		}
    64  	}
    65  	return result
    66  }
    67  
    68  func matchLastNameSegment(name string) string {
    69  	var result = make([]byte, 0)
    70  	for i := len(name) - 1; i >= 0; i-- {
    71  		aChar := string(name[i : i+1])
    72  		if aChar != "." {
    73  			result = append(result, byte(aChar[0]))
    74  		}
    75  		if strings.ToUpper(aChar) == aChar || aChar == "." {
    76  			ReverseSlice(result)
    77  			return string(result)
    78  		}
    79  	}
    80  	return name
    81  }
    82  
    83  //NewFieldInfo creates a new field info.
    84  func NewFieldInfo(field *ast.Field) *FieldInfo {
    85  	return NewFieldInfoByIndex(field, 0)
    86  }
    87  
    88  //NewFieldInfoByIndex creates a new field info.
    89  func NewFieldInfoByIndex(field *ast.Field, index int) *FieldInfo {
    90  	result := &FieldInfo{
    91  		Name:     "",
    92  		TypeName: types.ExprString(field.Type),
    93  	}
    94  
    95  	if len(field.Names) > 0 {
    96  		result.Name = field.Names[index].Name
    97  	} else {
    98  		result.Name = strings.Replace(strings.Replace(result.TypeName, "[]", "", len(result.TypeName)), "*", "", len(result.TypeName))
    99  		result.IsAnonymous = true
   100  	}
   101  	_, result.IsMap = field.Type.(*ast.MapType)
   102  	var arrayType *ast.ArrayType
   103  	if arrayType, result.IsSlice = field.Type.(*ast.ArrayType); result.IsSlice {
   104  		switch x := arrayType.Elt.(type) {
   105  		case *ast.Ident:
   106  			result.ComponentType = x.Name
   107  		case *ast.StarExpr:
   108  			switch y := x.X.(type) {
   109  			case *ast.Ident:
   110  				result.ComponentType = y.Name
   111  			case *ast.SelectorExpr:
   112  				result.ComponentType = y.X.(*ast.Ident).Name + "." + y.Sel.Name
   113  			}
   114  			result.IsPointerComponent = true
   115  		case *ast.SelectorExpr:
   116  			result.ComponentType = x.X.(*ast.Ident).Name + "." + x.Sel.Name
   117  		}
   118  	}
   119  	_, result.IsPointer = field.Type.(*ast.StarExpr)
   120  	_, result.IsChannel = field.Type.(*ast.ChanType)
   121  	if selector, ok := field.Type.(*ast.SelectorExpr); ok {
   122  		result.TypePackage = types.ExprString(selector.X)
   123  	}
   124  	if result.IsPointer {
   125  		if pointerExpr, casted := field.Type.(*ast.StarExpr); casted {
   126  			if identExpr, ok := pointerExpr.X.(*ast.Ident); ok {
   127  				result.TypeName = identExpr.Name
   128  			}
   129  		}
   130  	} else if identExpr, ok := field.Type.(*ast.Ident); ok {
   131  		result.TypeName = identExpr.Name
   132  	}
   133  
   134  	if field.Tag != nil {
   135  		result.Tag = field.Tag.Value
   136  	}
   137  	if mapType, ok := field.Type.(*ast.MapType); ok {
   138  		result.KeyTypeName = types.ExprString(mapType.Key)
   139  		result.ValueTypeName = types.ExprString(mapType.Value)
   140  	}
   141  
   142  	if strings.Contains(result.TypeName, "...") {
   143  		result.IsVariant = true
   144  		result.TypeName = strings.Replace(result.TypeName, "...", "[]", 1)
   145  	}
   146  
   147  	if index := strings.Index(result.TypeName, "."); index != -1 {
   148  		from := 0
   149  		if result.IsPointer {
   150  			from = 1
   151  		}
   152  		result.TypePackage = string(result.TypeName[from:index])
   153  	}
   154  	return result
   155  }
   156  
   157  //FunctionInfo represents a function info
   158  type FunctionInfo struct {
   159  	Name             string
   160  	ReceiverTypeName string
   161  	ParameterFields  []*FieldInfo
   162  	ResultsFields    []*FieldInfo
   163  	*FileInfo
   164  }
   165  
   166  //NewFunctionInfo create a new function
   167  func NewFunctionInfo(funcDeclaration *ast.FuncDecl, owner *FileInfo) *FunctionInfo {
   168  	result := &FunctionInfo{
   169  		Name:            "",
   170  		ParameterFields: make([]*FieldInfo, 0),
   171  		ResultsFields:   make([]*FieldInfo, 0),
   172  	}
   173  
   174  	if funcDeclaration.Name != nil {
   175  		result.Name = funcDeclaration.Name.Name
   176  	}
   177  	if funcDeclaration.Recv != nil {
   178  		receiverType := funcDeclaration.Recv.List[0].Type
   179  		if ident, ok := receiverType.(*ast.Ident); ok {
   180  			result.ReceiverTypeName = ident.Name
   181  		} else if startExpr, ok := receiverType.(*ast.StarExpr); ok {
   182  			if ident, ok := startExpr.X.(*ast.Ident); ok {
   183  				result.ReceiverTypeName = ident.Name
   184  			}
   185  		}
   186  	}
   187  	return result
   188  }
   189  
   190  //TypeInfo represents a struct info
   191  type TypeInfo struct {
   192  	Name                   string
   193  	Package                string
   194  	FileName               string
   195  	Comment                string
   196  	IsSlice                bool
   197  	IsMap                  bool
   198  	IsStruct               bool
   199  	IsInterface            bool
   200  	IsDerived              bool
   201  	ComponentType          string
   202  	IsPointerComponentType bool
   203  	Derived                string
   204  	KeyTypeName            string
   205  	ValueTypeName          string
   206  	Settings               map[string]string
   207  	fields                 []*FieldInfo
   208  	indexedField           map[string]*FieldInfo
   209  	receivers              []*FunctionInfo
   210  	indexedReceiver        map[string]*FunctionInfo
   211  	rcv                    *FunctionInfo
   212  }
   213  
   214  //AddFields appends fileds to structinfo
   215  func (s *TypeInfo) AddFields(fields ...*FieldInfo) {
   216  	s.fields = append(s.fields, fields...)
   217  	for _, field := range fields {
   218  		s.indexedField[field.Name] = field
   219  	}
   220  }
   221  
   222  //Field returns filedinfo for supplied file name
   223  func (s *TypeInfo) Field(name string) *FieldInfo {
   224  	return s.indexedField[name]
   225  }
   226  
   227  //Fields returns all fields
   228  func (s *TypeInfo) Fields() []*FieldInfo {
   229  	return s.fields
   230  }
   231  
   232  //HasField returns true if struct has passed in field.
   233  func (s *TypeInfo) HasField(name string) bool {
   234  	_, found := s.indexedField[name]
   235  	return found
   236  }
   237  
   238  //Receivers returns struct functions
   239  func (s *TypeInfo) Receivers() []*FunctionInfo {
   240  	return s.receivers
   241  }
   242  
   243  //Receiver returns receiver for passed in name
   244  func (s *TypeInfo) Receiver(name string) *FunctionInfo {
   245  	return s.indexedReceiver[name]
   246  }
   247  
   248  //HasReceiver returns true if receiver is defined for struct
   249  func (s *TypeInfo) HasReceiver(name string) bool {
   250  	_, found := s.indexedReceiver[name]
   251  	return found
   252  }
   253  
   254  //AddReceivers adds receiver for the struct
   255  func (s *TypeInfo) AddReceivers(receivers ...*FunctionInfo) {
   256  	s.receivers = append(s.receivers, receivers...)
   257  	for _, receiver := range receivers {
   258  		s.indexedReceiver[receiver.Name] = receiver
   259  	}
   260  }
   261  
   262  //NewTypeInfo creates a new struct info
   263  func NewTypeInfo(name string) *TypeInfo {
   264  	return &TypeInfo{Name: name,
   265  		fields:          make([]*FieldInfo, 0),
   266  		receivers:       make([]*FunctionInfo, 0),
   267  		indexedReceiver: make(map[string]*FunctionInfo),
   268  		indexedField:    make(map[string]*FieldInfo),
   269  		Settings:        make(map[string]string)}
   270  }
   271  
   272  //FileInfo represent hold definition about all defined types and its receivers in a file
   273  type FileInfo struct {
   274  	basePath            string
   275  	filename            string
   276  	types               map[string]*TypeInfo
   277  	functions           map[string][]*FunctionInfo
   278  	packageName         string
   279  	currentTypInfo      *TypeInfo
   280  	fileSet             *token.FileSet
   281  	currentFunctionInfo *FunctionInfo
   282  	Imports             map[string]string
   283  }
   284  
   285  //Type returns a type info for passed in name
   286  func (f *FileInfo) Type(name string) *TypeInfo {
   287  	return f.types[name]
   288  }
   289  
   290  //Type returns a struct info for passed in name
   291  func (f *FileInfo) addFunction(funcion *FunctionInfo) {
   292  	functions, found := f.functions[funcion.ReceiverTypeName]
   293  	if !found {
   294  		functions = make([]*FunctionInfo, 0)
   295  		f.functions[funcion.ReceiverTypeName] = functions
   296  	}
   297  	f.functions[funcion.ReceiverTypeName] = append(f.functions[funcion.ReceiverTypeName], funcion)
   298  }
   299  
   300  //Types returns all struct info
   301  func (f *FileInfo) Types() []*TypeInfo {
   302  	var result = make([]*TypeInfo, 0)
   303  	for _, v := range f.types {
   304  		result = append(result, v)
   305  	}
   306  	return result
   307  }
   308  
   309  //HasType returns truc if struct info is defined in a file
   310  func (f *FileInfo) HasType(name string) bool {
   311  	_, found := f.types[name]
   312  	return found
   313  }
   314  
   315  //readComment reads comment from the position
   316  func (f *FileInfo) readComment(pos token.Pos) string {
   317  	position := f.fileSet.Position(pos)
   318  	fileName := path.Join(f.basePath, f.filename)
   319  	content, err := ioutil.ReadFile(fileName)
   320  	if err != nil {
   321  		panic("Unable to open file " + fileName)
   322  	}
   323  	line := strings.Split(string(content), "\n")[position.Line-1]
   324  	commentPosition := strings.LastIndex(line, "//")
   325  	if commentPosition != -1 {
   326  		return line[commentPosition+2:]
   327  	}
   328  	return ""
   329  }
   330  
   331  //toFieldInfoSlice converts filedList to FiledInfo slice.
   332  func toFieldInfoSlice(source *ast.FieldList) []*FieldInfo {
   333  	var result = make([]*FieldInfo, 0)
   334  	if source == nil || len(source.List) == 0 {
   335  		return result
   336  	}
   337  	for _, field := range source.List {
   338  		if len(field.Names) > 0 {
   339  			for i := range field.Names {
   340  				result = append(result, NewFieldInfoByIndex(field, i))
   341  			}
   342  		} else {
   343  			result = append(result, NewFieldInfoByIndex(field, 0))
   344  		}
   345  	}
   346  	return result
   347  }
   348  
   349  //toFunctionInfos convers filedList to function info slice.
   350  func toFunctionInfos(source *ast.FieldList, owner *FileInfo) []*FunctionInfo {
   351  	var result = make([]*FunctionInfo, 0)
   352  	if source == nil || len(source.List) == 0 {
   353  		return result
   354  	}
   355  	for _, field := range source.List {
   356  		result = append(result, NewFunctionInfoFromField(field, owner))
   357  	}
   358  	return result
   359  }
   360  
   361  //Visit visits ast node to extract struct details from the passed file
   362  func (f *FileInfo) Visit(node ast.Node) ast.Visitor {
   363  	if node != nil {
   364  		if os.Getenv("AST_DEBUG") == "1" {
   365  			fmt.Printf("%T %v\n",node,node)
   366  		}
   367  //TODO refactor this mess !!!!
   368  		switch value := node.(type) {
   369  		case *ast.TypeSpec:
   370  			typeName := value.Name.Name
   371  			typeInfo := NewTypeInfo(typeName)
   372  			typeInfo.Package = f.packageName
   373  			typeInfo.FileName = f.filename
   374  
   375  			switch typeValue := value.Type.(type) {
   376  			case *ast.ArrayType:
   377  				typeInfo.IsSlice = true
   378  				if ident, ok := typeValue.Elt.(*ast.Ident); ok {
   379  					typeInfo.ComponentType = ident.Name
   380  				} else if startExpr, ok := typeValue.Elt.(*ast.StarExpr); ok {
   381  					if ident, ok := startExpr.X.(*ast.Ident); ok {
   382  						typeInfo.ComponentType = ident.Name
   383  					}
   384  					typeInfo.IsPointerComponentType = true
   385  				}
   386  			case *ast.StructType:
   387  				typeInfo.IsStruct = true
   388  			case *ast.InterfaceType:
   389  				typeInfo.IsInterface = true
   390  			case *ast.Ident:
   391  				typeInfo.Derived = typeValue.Name
   392  				typeInfo.IsDerived = true
   393  			}
   394  			f.currentTypInfo = typeInfo
   395  			f.types[typeName] = typeInfo
   396  		case *ast.StructType:
   397  			if f.currentTypInfo != nil { //TODO fixme - understand why current type would be nil
   398  				f.currentTypInfo.Comment = f.readComment(value.Pos())
   399  				f.currentTypInfo.AddFields(toFieldInfoSlice(value.Fields)...)
   400  			}
   401  		case *ast.FuncDecl:
   402  			functionInfo := NewFunctionInfo(value, f)
   403  			functionInfo.FileInfo = f
   404  			f.currentFunctionInfo = functionInfo
   405  			if len(functionInfo.ReceiverTypeName) > 0 {
   406  				f.addFunction(functionInfo)
   407  			}
   408  		case *ast.MapType:
   409  			if f.currentTypInfo == nil {
   410  				break
   411  			}
   412  			f.currentTypInfo.IsMap = true
   413  			if keyTypeID, ok := value.Key.(*ast.Ident); ok {
   414  				f.currentTypInfo.KeyTypeName = keyTypeID.Name
   415  			}
   416  			switch v := value.Value.(type) {
   417  			case *ast.Ident:
   418  				f.currentTypInfo.ValueTypeName = v.Name
   419  			case *ast.StarExpr:
   420  				componentTypeName := "*"
   421  				switch y :=v.X.(type) {
   422  				case *ast.Ident:
   423  					componentTypeName += y.Name
   424  				case *ast.SelectorExpr:
   425  					componentTypeName += y.X.(*ast.Ident).Name + "." + y.Sel.Name
   426  				}
   427  				f.currentTypInfo.ValueTypeName = componentTypeName
   428  
   429  			case *ast.ArrayType:
   430  				componentTypeName := ""
   431  				switch compType := v.Elt.(type) {
   432  				case *ast.StarExpr:
   433  					componentTypeName += "*"
   434  					switch y := compType.X.(type) {
   435  					case *ast.Ident:
   436  						componentTypeName += y.Name
   437  					case *ast.SelectorExpr:
   438  						componentTypeName += y.X.(*ast.Ident).Name + "." + y.Sel.Name
   439  					}
   440  				case *ast.Ident:
   441  					componentTypeName = compType.Name
   442  				}
   443  				f.currentTypInfo.ValueTypeName = "[]" + componentTypeName
   444  
   445  			}
   446  		case *ast.FuncType:
   447  
   448  			if f.currentFunctionInfo != nil {
   449  				if value.Params != nil {
   450  					f.currentFunctionInfo.ParameterFields = toFieldInfoSlice(value.Params)
   451  				}
   452  
   453  				if value.Results != nil {
   454  					f.currentFunctionInfo.ResultsFields = toFieldInfoSlice(value.Results)
   455  				}
   456  				f.currentFunctionInfo = nil
   457  			}
   458  		case *ast.FieldList:
   459  			if f.currentTypInfo != nil && f.currentTypInfo.IsInterface {
   460  				f.currentTypInfo.receivers = toFunctionInfos(value, f)
   461  				f.currentTypInfo = nil
   462  			}
   463  		case *ast.ImportSpec:
   464  			if value.Name != nil && value.Name.String() != "" {
   465  				f.Imports[value.Name.String()] = value.Path.Value
   466  			} else {
   467  				_, name := path.Split(value.Path.Value)
   468  				name = strings.Replace(name, `"`, "", 2)
   469  				f.Imports[name] = value.Path.Value
   470  			}
   471  		}
   472  
   473  	}
   474  	return f
   475  }
   476  
   477  //NewFileInfo creates a new file info.
   478  func NewFileInfo(basePath, packageName, filename string, fileSet *token.FileSet) *FileInfo {
   479  	result := &FileInfo{
   480  		basePath:    basePath,
   481  		filename:    filename,
   482  		packageName: packageName,
   483  		types:       make(map[string]*TypeInfo),
   484  		functions:   make(map[string][]*FunctionInfo),
   485  		Imports:     make(map[string]string),
   486  		fileSet:     fileSet}
   487  	return result
   488  }
   489  
   490  //FileSetInfo represents a fileset info storing information about go file with their struct definition
   491  type FileSetInfo struct {
   492  	files map[string]*FileInfo
   493  }
   494  
   495  //FileInfo returns fileinfo for supplied file name
   496  func (f *FileSetInfo) FileInfo(name string) *FileInfo {
   497  	return f.files[name]
   498  }
   499  
   500  //FilesInfo returns all files info.
   501  func (f *FileSetInfo) FilesInfo() map[string]*FileInfo {
   502  	return f.files
   503  }
   504  
   505  //Type returns type info for passed in type  name.
   506  func (f *FileSetInfo) Type(name string) *TypeInfo {
   507  	if pointerIndex := strings.LastIndex(name, "*"); pointerIndex != -1 {
   508  		name = name[pointerIndex+1:]
   509  	}
   510  	for _, v := range f.files {
   511  		if v.HasType(name) {
   512  			return v.Type(name)
   513  		}
   514  	}
   515  	return nil
   516  }
   517  
   518  //NewFileSetInfo creates a new fileset info
   519  func NewFileSetInfo(baseDir string) (*FileSetInfo, error) {
   520  	fileSet := token.NewFileSet()
   521  	pkgs, err := parser.ParseDir(fileSet, baseDir, nil, parser.ParseComments)
   522  	if err != nil {
   523  		return nil, fmt.Errorf("failed to parse path %v: %v", baseDir, err)
   524  	}
   525  
   526  	var result = &FileSetInfo{
   527  		files: make(map[string]*FileInfo),
   528  	}
   529  	for packageName, pkg := range pkgs {
   530  		for filename, file := range pkg.Files {
   531  			filename := filepath.Base(filename)
   532  			fileInfo := NewFileInfo(baseDir, packageName, filename, fileSet)
   533  			ast.Walk(fileInfo, file)
   534  			result.files[filename] = fileInfo
   535  		}
   536  	}
   537  
   538  	for _, fileInfo := range result.files {
   539  
   540  		for k, functionsInfo := range fileInfo.functions {
   541  			typeInfo := result.Type(k)
   542  			if typeInfo != nil && typeInfo.IsStruct {
   543  				typeInfo.AddReceivers(functionsInfo...)
   544  			}
   545  		}
   546  
   547  	}
   548  	return result, nil
   549  }