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 }