github.com/terhitormanen/cmd@v1.1.4/model/source_info.go (about) 1 package model 2 3 // SourceInfo is the top-level struct containing all extracted information 4 // about the app source code, used to generate main.go. 5 import ( 6 "path/filepath" 7 "strings" 8 "unicode" 9 10 "github.com/terhitormanen/cmd/utils" 11 ) 12 13 type SourceInfo struct { 14 // StructSpecs lists type info for all structs found under the code paths. 15 // They may be queried to determine which ones (transitively) embed certain types. 16 StructSpecs []*TypeInfo 17 // ValidationKeys provides a two-level lookup. The keys are: 18 // 1. The fully-qualified function name, 19 // e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action" 20 // 2. Within that func's file, the line number of the (overall) expression statement. 21 // e.g. the line returned from runtime.Caller() 22 // The result of the lookup the name of variable being validated. 23 ValidationKeys map[string]map[int]string 24 // A list of import paths. 25 // Revel notices files with an init() function and imports that package. 26 InitImportPaths []string 27 28 // controllerSpecs lists type info for all structs found under 29 // app/controllers/... that embed (directly or indirectly) revel.Controller 30 controllerSpecs []*TypeInfo 31 // testSuites list the types that constitute the set of application tests. 32 testSuites []*TypeInfo 33 // packageMap a map of import to system directory (if available) 34 PackageMap map[string]string 35 } 36 37 // TypesThatEmbed returns all types that (directly or indirectly) embed the 38 // target type, which must be a fully qualified type name, 39 // e.g. "github.com/revel/revel.Controller". 40 func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) { 41 // Do a search in the "embedded type graph", starting with the target type. 42 var ( 43 nodeQueue = []string{targetType} 44 processed []string 45 ) 46 for len(nodeQueue) > 0 { 47 typeSimpleName := nodeQueue[0] 48 nodeQueue = nodeQueue[1:] 49 processed = append(processed, typeSimpleName) 50 51 // Look through all known structs. 52 for _, spec := range s.StructSpecs { 53 // If this one has been processed or is already in nodeQueue, then skip it. 54 if utils.ContainsString(processed, spec.String()) || 55 utils.ContainsString(nodeQueue, spec.String()) { 56 continue 57 } 58 59 // Look through the embedded types to see if the current type is among them. 60 for _, embeddedType := range spec.EmbeddedTypes { 61 // If so, add this type's simple name to the nodeQueue, and its spec to 62 // the filtered list. 63 if typeSimpleName == embeddedType.String() { 64 nodeQueue = append(nodeQueue, spec.String()) 65 filtered = append(filtered, spec) 66 break 67 } 68 } 69 } 70 } 71 72 // Strip out any specifications that contain a lower case 73 for exit := false; !exit; exit = true { 74 for i, filteredItem := range filtered { 75 if unicode.IsLower([]rune(filteredItem.StructName)[0]) { 76 utils.Logger.Info("Debug: Skipping adding spec for unexported type", 77 "type", filteredItem.StructName, 78 "package", filteredItem.ImportPath) 79 filtered = append(filtered[:i], filtered[i+1:]...) 80 //nolint:ineffassign // huh? 81 exit = false 82 break 83 } 84 } 85 } 86 87 // Check for any missed types that where from expected packages 88 for _, spec := range s.StructSpecs { 89 if spec.PackageName == packageFilter { 90 found := false 91 unfoundNames := "" 92 for _, filteredItem := range filtered { 93 if filteredItem.StructName == spec.StructName { 94 found = true 95 break 96 } else { 97 unfoundNames += filteredItem.StructName + "," 98 } 99 } 100 101 // Report non controller structures in controller folder. 102 if !found && !strings.HasPrefix(spec.StructName, "Test") { 103 utils.Logger.Warn("Type found in package: "+packageFilter+ 104 ", but did not embed from: "+filepath.Base(targetType), 105 "name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames) 106 } 107 } 108 } 109 return 110 } 111 112 // ControllerSpecs returns the all the controllers that embeds 113 // `revel.Controller`. 114 func (s *SourceInfo) ControllerSpecs() []*TypeInfo { 115 utils.Logger.Info("Scanning controller specifications for types ", "typePath", RevelImportPath+".Controller", "speclen", len(s.controllerSpecs)) 116 if s.controllerSpecs == nil { 117 s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers") 118 } 119 return s.controllerSpecs 120 } 121 122 // TestSuites returns the all the Application tests that embeds 123 // `testing.TestSuite`. 124 func (s *SourceInfo) TestSuites() []*TypeInfo { 125 if s.testSuites == nil { 126 s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite") 127 } 128 return s.testSuites 129 } 130 131 func (s *SourceInfo) Merge(srcInfo2 *SourceInfo) { 132 s.StructSpecs = append(s.StructSpecs, srcInfo2.StructSpecs...) 133 s.InitImportPaths = append(s.InitImportPaths, srcInfo2.InitImportPaths...) 134 for k, v := range srcInfo2.ValidationKeys { 135 if _, ok := s.ValidationKeys[k]; ok { 136 utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k) 137 continue 138 } 139 s.ValidationKeys[k] = v 140 } 141 }