github.com/jd-ly/cmd@v1.0.10/parser2/source_info_processor.go (about) 1 package parser2 2 3 import ( 4 "github.com/jd-ly/cmd/utils" 5 "golang.org/x/tools/go/packages" 6 "github.com/jd-ly/cmd/model" 7 "go/ast" 8 "go/token" 9 "strings" 10 "path/filepath" 11 "github.com/jd-ly/cmd/logger" 12 ) 13 14 type ( 15 SourceInfoProcessor struct { 16 sourceProcessor *SourceProcessor 17 } 18 ) 19 20 func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor { 21 return &SourceInfoProcessor{sourceProcessor:sourceProcessor} 22 } 23 24 func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) { 25 sourceInfo = &model.SourceInfo{ 26 ValidationKeys: map[string]map[int]string{}, 27 } 28 var ( 29 isController = strings.HasSuffix(p.PkgPath, "/controllers") || 30 strings.Contains(p.PkgPath, "/controllers/") 31 isTest = strings.HasSuffix(p.PkgPath, "/tests") || 32 strings.Contains(p.PkgPath, "/tests/") 33 methodMap = map[string][]*model.MethodSpec{} 34 ) 35 localImportMap := map[string]string{} 36 log := s.sourceProcessor.log.New("package", p.PkgPath) 37 log.Info("Processing package") 38 for _, tree := range p.Syntax { 39 for _, decl := range tree.Decls { 40 41 s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename) 42 if !s.addImport(decl, p, localImportMap, log) { 43 continue 44 } 45 spec, found := s.getStructTypeDecl(decl, p.Fset) 46 //log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found) 47 if found { 48 if isController || isTest { 49 controllerSpec := s.getControllerSpec(spec, p, localImportMap) 50 sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec) 51 } 52 } else { 53 // Not a type definition, this could be a method for a controller try to extract that 54 // Func declaration? 55 funcDecl, ok := decl.(*ast.FuncDecl) 56 if !ok { 57 continue 58 } 59 // This could be a controller action endpoint, check and add if needed 60 if isController && 61 funcDecl.Recv != nil && // Must have a receiver 62 funcDecl.Name.IsExported() && // be public 63 funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 { 64 // return one result 65 if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil { 66 methodMap[receiver] = append(methodMap[receiver], m) 67 log.Info("Added method map to ", "receiver", receiver, "method", m.Name) 68 } 69 } 70 // Check for validation 71 if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 { 72 sourceInfo.ValidationKeys[p.PkgPath + "." + s.getFuncName(funcDecl)] = lineKeyMap 73 } 74 if funcDecl.Name.Name == "init" { 75 sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath) 76 } 77 } 78 } 79 } 80 81 // Add the method specs to the struct specs. 82 for _, spec := range sourceInfo.StructSpecs { 83 spec.MethodSpecs = methodMap[spec.StructName] 84 } 85 86 return 87 } 88 // Scan app source code for calls to X.Y(), where X is of type *Validation. 89 // 90 // Recognize these scenarios: 91 // - "Y" = "Validation" and is a member of the receiver. 92 // (The common case for inline validation) 93 // - "X" is passed in to the func as a parameter. 94 // (For structs implementing Validated) 95 // 96 // The line number to which a validation call is attributed is that of the 97 // surrounding ExprStmt. This is so that it matches what runtime.Callers() 98 // reports. 99 // 100 // The end result is that we can set the default validation key for each call to 101 // be the same as the local variable. 102 func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) (map[int]string) { 103 var ( 104 lineKeys = make(map[int]string) 105 106 // Check the func parameters and the receiver's members for the *revel.Validation type. 107 validationParam = s.getValidationParameter(funcDecl) 108 ) 109 110 ast.Inspect(funcDecl.Body, func(node ast.Node) bool { 111 // e.g. c.Validation.Required(arg) or v.Required(arg) 112 callExpr, ok := node.(*ast.CallExpr) 113 if !ok { 114 return true 115 } 116 117 // e.g. c.Validation.Required or v.Required 118 funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr) 119 if !ok { 120 return true 121 } 122 123 switch x := funcSelector.X.(type) { 124 case *ast.SelectorExpr: // e.g. c.Validation 125 if x.Sel.Name != "Validation" { 126 return true 127 } 128 129 case *ast.Ident: // e.g. v 130 if validationParam == nil || x.Obj != validationParam { 131 return true 132 } 133 134 default: 135 return true 136 } 137 138 if len(callExpr.Args) == 0 { 139 return true 140 } 141 142 // Given the validation expression, extract the key. 143 key := callExpr.Args[0] 144 switch expr := key.(type) { 145 case *ast.BinaryExpr: 146 // If the argument is a binary expression, take the first expression. 147 // (e.g. c.Validation.Required(myName != "")) 148 key = expr.X 149 case *ast.UnaryExpr: 150 // If the argument is a unary expression, drill in. 151 // (e.g. c.Validation.Required(!myBool) 152 key = expr.X 153 case *ast.BasicLit: 154 // If it's a literal, skip it. 155 return true 156 } 157 158 if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid { 159 lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("") 160 } else { 161 s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath, 162 "line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String()) 163 } 164 return true 165 }) 166 167 return lineKeys 168 169 } 170 // Check to see if there is a *revel.Validation as an argument. 171 func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object { 172 for _, field := range funcDecl.Type.Params.List { 173 starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation 174 if !ok { 175 continue 176 } 177 178 selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation 179 if !ok { 180 continue 181 } 182 183 xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev 184 if !ok { 185 continue 186 } 187 188 if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath { 189 return field.Names[0].Obj 190 } 191 } 192 return nil 193 } 194 func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package, localImportMap map[string]string) (method *model.MethodSpec, recvTypeName string) { 195 selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr) 196 if !ok { 197 return 198 } 199 if selExpr.Sel.Name != "Result" { 200 return 201 } 202 if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath { 203 return 204 } 205 method = &model.MethodSpec{ 206 Name: funcDecl.Name.Name, 207 } 208 209 // Add a description of the arguments to the method. 210 for _, field := range funcDecl.Type.Params.List { 211 for _, name := range field.Names { 212 var importPath string 213 typeExpr := model.NewTypeExprFromAst(p.Name, field.Type) 214 if !typeExpr.Valid { 215 utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl)) 216 return // We didn't understand one of the args. Ignore this action. 217 } 218 // Local object 219 if typeExpr.PkgName == p.Name { 220 importPath = p.PkgPath 221 } else if typeExpr.PkgName != "" { 222 var ok bool 223 if importPath, ok = localImportMap[typeExpr.PkgName]; !ok { 224 if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok { 225 utils.Logger.Error("Unable to find import", "importMap", s.sourceProcessor.importMap, "localimport", localImportMap) 226 utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName("")) 227 } 228 } 229 } 230 method.Args = append(method.Args, &model.MethodArg{ 231 Name: name.Name, 232 TypeExpr: typeExpr, 233 ImportPath: importPath, 234 }) 235 } 236 } 237 238 // Add a description of the calls to Render from the method. 239 // Inspect every node (e.g. always return true). 240 method.RenderCalls = []*model.MethodCall{} 241 ast.Inspect(funcDecl.Body, func(node ast.Node) bool { 242 // Is it a function call? 243 callExpr, ok := node.(*ast.CallExpr) 244 if !ok { 245 return true 246 } 247 248 // Is it calling (*Controller).Render? 249 selExpr, ok := callExpr.Fun.(*ast.SelectorExpr) 250 if !ok { 251 return true 252 } 253 254 // The type of the receiver is not easily available, so just store every 255 // call to any method called Render. 256 if selExpr.Sel.Name != "Render" { 257 return true 258 } 259 260 // Add this call's args to the renderArgs. 261 pos := p.Fset.Position(callExpr.Lparen) 262 methodCall := &model.MethodCall{ 263 Line: pos.Line, 264 Names: []string{}, 265 } 266 for _, arg := range callExpr.Args { 267 argIdent, ok := arg.(*ast.Ident) 268 if !ok { 269 continue 270 } 271 methodCall.Names = append(methodCall.Names, argIdent.Name) 272 } 273 method.RenderCalls = append(method.RenderCalls, methodCall) 274 return true 275 }) 276 277 var recvType = funcDecl.Recv.List[0].Type 278 if recvStarType, ok := recvType.(*ast.StarExpr); ok { 279 recvTypeName = recvStarType.X.(*ast.Ident).Name 280 } else { 281 recvTypeName = recvType.(*ast.Ident).Name 282 } 283 return 284 } 285 func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) { 286 structType := spec.Type.(*ast.StructType) 287 288 // At this point we know it's a type declaration for a struct. 289 // Fill in the rest of the info by diving into the fields. 290 // Add it provisionally to the Controller list -- it's later filtered using field info. 291 controllerSpec = &model.TypeInfo{ 292 StructName: spec.Name.Name, 293 ImportPath: p.PkgPath, 294 PackageName: p.Name, 295 } 296 log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line) 297 for _, field := range structType.Fields.List { 298 // If field.Names is set, it's not an embedded type. 299 if field.Names != nil { 300 continue 301 } 302 303 // A direct "sub-type" has an ast.Field as either: 304 // Ident { "AppController" } 305 // SelectorExpr { "rev", "Controller" } 306 // Additionally, that can be wrapped by StarExprs. 307 fieldType := field.Type 308 pkgName, typeName := func() (string, string) { 309 // Drill through any StarExprs. 310 for { 311 if starExpr, ok := fieldType.(*ast.StarExpr); ok { 312 fieldType = starExpr.X 313 continue 314 } 315 break 316 } 317 318 // If the embedded type is in the same package, it's an Ident. 319 if ident, ok := fieldType.(*ast.Ident); ok { 320 return "", ident.Name 321 } 322 323 if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok { 324 if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok { 325 return pkgIdent.Name, selectorExpr.Sel.Name 326 } 327 } 328 return "", "" 329 }() 330 331 // If a typename wasn't found, skip it. 332 if typeName == "" { 333 continue 334 } 335 336 // Find the import path for this type. 337 // If it was referenced without a package name, use the current package import path. 338 // Else, look up the package's import path by name. 339 var importPath string 340 if pkgName == "" { 341 importPath = p.PkgPath 342 } else { 343 var ok bool 344 if importPath, ok = localImportMap[pkgName]; !ok { 345 log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", ) 346 if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok { 347 log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", ) 348 continue 349 } 350 } 351 } 352 353 controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{ 354 ImportPath: importPath, 355 StructName: typeName, 356 }) 357 } 358 s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath) 359 return 360 } 361 func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) { 362 genDecl, ok := decl.(*ast.GenDecl) 363 if !ok { 364 return 365 } 366 367 if genDecl.Tok != token.TYPE { 368 return 369 } 370 371 if len(genDecl.Specs) == 0 { 372 utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line) 373 return 374 } 375 376 spec = genDecl.Specs[0].(*ast.TypeSpec) 377 _, found = spec.Type.(*ast.StructType) 378 379 return 380 381 } 382 func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string { 383 prefix := "" 384 if funcDecl.Recv != nil { 385 recvType := funcDecl.Recv.List[0].Type 386 if recvStarType, ok := recvType.(*ast.StarExpr); ok { 387 prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")" 388 } else { 389 prefix = recvType.(*ast.Ident).Name 390 } 391 prefix += "." 392 } 393 return prefix + funcDecl.Name.Name 394 } 395 func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) { 396 shouldContinue = true 397 genDecl, ok := decl.(*ast.GenDecl) 398 if !ok { 399 return 400 } 401 402 if genDecl.Tok == token.IMPORT { 403 shouldContinue = false 404 for _, spec := range genDecl.Specs { 405 importSpec := spec.(*ast.ImportSpec) 406 //fmt.Printf("*** import specification %#v\n", importSpec) 407 var pkgAlias string 408 if importSpec.Name != nil { 409 pkgAlias = importSpec.Name.Name 410 if pkgAlias == "_" { 411 continue 412 } 413 } 414 quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\"" 415 fullPath := quotedPath[1 : len(quotedPath) - 1] // Remove the quotes 416 if pkgAlias == "" { 417 pkgAlias = fullPath 418 if index := strings.LastIndex(pkgAlias, "/"); index > 0 { 419 pkgAlias = pkgAlias[index + 1:] 420 } 421 } 422 localImportMap[pkgAlias] = fullPath 423 } 424 425 } 426 return 427 }