github.com/mlmmr/revel-cmd@v0.21.2-0.20191112133115-68d8795776dd/parser/appends.go (about) 1 package parser 2 3 import ( 4 "go/ast" 5 "github.com/mlmmr/revel-cmd/utils" 6 "github.com/mlmmr/revel-cmd/model" 7 "go/token" 8 ) 9 10 // If this Decl is a struct type definition, it is summarized and added to specs. 11 // Else, specs is returned unchanged. 12 func appendStruct(fileName string, specs []*model.TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*model.TypeInfo { 13 // Filter out non-Struct type declarations. 14 spec, found := getStructTypeDecl(decl, fset) 15 if !found { 16 return specs 17 } 18 19 structType := spec.Type.(*ast.StructType) 20 21 // At this point we know it's a type declaration for a struct. 22 // Fill in the rest of the info by diving into the fields. 23 // Add it provisionally to the Controller list -- it's later filtered using field info. 24 controllerSpec := &model.TypeInfo{ 25 StructName: spec.Name.Name, 26 ImportPath: pkgImportPath, 27 PackageName: pkg.Name, 28 } 29 30 for _, field := range structType.Fields.List { 31 // If field.Names is set, it's not an embedded type. 32 if field.Names != nil { 33 continue 34 } 35 36 // A direct "sub-type" has an ast.Field as either: 37 // Ident { "AppController" } 38 // SelectorExpr { "rev", "Controller" } 39 // Additionally, that can be wrapped by StarExprs. 40 fieldType := field.Type 41 pkgName, typeName := func() (string, string) { 42 // Drill through any StarExprs. 43 for { 44 if starExpr, ok := fieldType.(*ast.StarExpr); ok { 45 fieldType = starExpr.X 46 continue 47 } 48 break 49 } 50 51 // If the embedded type is in the same package, it's an Ident. 52 if ident, ok := fieldType.(*ast.Ident); ok { 53 return "", ident.Name 54 } 55 56 if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok { 57 if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok { 58 return pkgIdent.Name, selectorExpr.Sel.Name 59 } 60 } 61 return "", "" 62 }() 63 64 // If a typename wasn't found, skip it. 65 if typeName == "" { 66 continue 67 } 68 69 // Find the import path for this type. 70 // If it was referenced without a package name, use the current package import path. 71 // Else, look up the package's import path by name. 72 var importPath string 73 if pkgName == "" { 74 importPath = pkgImportPath 75 } else { 76 var ok bool 77 if importPath, ok = imports[pkgName]; !ok { 78 utils.Logger.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName) 79 continue 80 } 81 } 82 83 controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{ 84 ImportPath: importPath, 85 StructName: typeName, 86 }) 87 } 88 89 return append(specs, controllerSpec) 90 } 91 92 // If decl is a Method declaration, it is summarized and added to the array 93 // underneath its receiver type. 94 // e.g. "Login" => {MethodSpec, MethodSpec, ..} 95 func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) { 96 // Func declaration? 97 funcDecl, ok := decl.(*ast.FuncDecl) 98 if !ok { 99 return 100 } 101 102 // Have a receiver? 103 if funcDecl.Recv == nil { 104 return 105 } 106 107 // Is it public? 108 if !funcDecl.Name.IsExported() { 109 return 110 } 111 112 // Does it return a Result? 113 if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 { 114 return 115 } 116 selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr) 117 if !ok { 118 return 119 } 120 if selExpr.Sel.Name != "Result" { 121 return 122 } 123 if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != model.RevelImportPath { 124 return 125 } 126 127 method := &model.MethodSpec{ 128 Name: funcDecl.Name.Name, 129 } 130 131 // Add a description of the arguments to the method. 132 for _, field := range funcDecl.Type.Params.List { 133 for _, name := range field.Names { 134 var importPath string 135 typeExpr := model.NewTypeExprFromAst(pkgName, field.Type) 136 if !typeExpr.Valid { 137 utils.Logger.Warnf("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl)) 138 return // We didn't understand one of the args. Ignore this action. 139 } 140 // Local object 141 if typeExpr.PkgName == pkgName { 142 importPath = pkgImportPath 143 } else if typeExpr.PkgName != "" { 144 var ok bool 145 if importPath, ok = imports[typeExpr.PkgName]; !ok { 146 utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName("")) 147 } 148 } 149 method.Args = append(method.Args, &model.MethodArg{ 150 Name: name.Name, 151 TypeExpr: typeExpr, 152 ImportPath: importPath, 153 }) 154 } 155 } 156 157 // Add a description of the calls to Render from the method. 158 // Inspect every node (e.g. always return true). 159 method.RenderCalls = []*model.MethodCall{} 160 ast.Inspect(funcDecl.Body, func(node ast.Node) bool { 161 // Is it a function call? 162 callExpr, ok := node.(*ast.CallExpr) 163 if !ok { 164 return true 165 } 166 167 // Is it calling (*Controller).Render? 168 selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) 169 if !ok { 170 return true 171 } 172 173 // The type of the receiver is not easily available, so just store every 174 // call to any method called Render. 175 if selExpr.Sel.Name != "Render" { 176 return true 177 } 178 179 // Add this call's args to the renderArgs. 180 pos := fset.Position(callExpr.Lparen) 181 methodCall := &model.MethodCall{ 182 Line: pos.Line, 183 Names: []string{}, 184 } 185 for _, arg := range callExpr.Args { 186 argIdent, ok := arg.(*ast.Ident) 187 if !ok { 188 continue 189 } 190 methodCall.Names = append(methodCall.Names, argIdent.Name) 191 } 192 method.RenderCalls = append(method.RenderCalls, methodCall) 193 return true 194 }) 195 196 var recvTypeName string 197 var recvType = funcDecl.Recv.List[0].Type 198 if recvStarType, ok := recvType.(*ast.StarExpr); ok { 199 recvTypeName = recvStarType.X.(*ast.Ident).Name 200 } else { 201 recvTypeName = recvType.(*ast.Ident).Name 202 } 203 204 mm[recvTypeName] = append(mm[recvTypeName], method) 205 } 206 207 // Combine the 2 source info models into one 208 func appendSourceInfo(srcInfo1, srcInfo2 *model.SourceInfo) *model.SourceInfo { 209 if srcInfo1 == nil { 210 return srcInfo2 211 } 212 213 srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...) 214 srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...) 215 for k, v := range srcInfo2.ValidationKeys { 216 if _, ok := srcInfo1.ValidationKeys[k]; ok { 217 utils.Logger.Warn("Warn: Key conflict when scanning validation calls:", "key", k) 218 continue 219 } 220 srcInfo1.ValidationKeys[k] = v 221 } 222 return srcInfo1 223 }