github.com/jd-ly/cmd@v1.0.10/parser/validation.go (about) 1 package parser 2 3 import ( 4 "github.com/jd-ly/cmd/model" 5 "github.com/jd-ly/cmd/utils" 6 "go/ast" 7 "go/token" 8 ) 9 10 // Scan app source code for calls to X.Y(), where X is of type *Validation. 11 // 12 // Recognize these scenarios: 13 // - "Y" = "Validation" and is a member of the receiver. 14 // (The common case for inline validation) 15 // - "X" is passed in to the func as a parameter. 16 // (For structs implementing Validated) 17 // 18 // The line number to which a validation call is attributed is that of the 19 // surrounding ExprStmt. This is so that it matches what runtime.Callers() 20 // reports. 21 // 22 // The end result is that we can set the default validation key for each call to 23 // be the same as the local variable. 24 func GetValidationKeys(fname string, fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string { 25 var ( 26 lineKeys = make(map[int]string) 27 28 // Check the func parameters and the receiver's members for the *revel.Validation type. 29 validationParam = getValidationParameter(funcDecl, imports) 30 ) 31 32 ast.Inspect(funcDecl.Body, func(node ast.Node) bool { 33 // e.g. c.Validation.Required(arg) or v.Required(arg) 34 callExpr, ok := node.(*ast.CallExpr) 35 if !ok { 36 return true 37 } 38 39 // e.g. c.Validation.Required or v.Required 40 funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr) 41 if !ok { 42 return true 43 } 44 45 switch x := funcSelector.X.(type) { 46 case *ast.SelectorExpr: // e.g. c.Validation 47 if x.Sel.Name != "Validation" { 48 return true 49 } 50 51 case *ast.Ident: // e.g. v 52 if validationParam == nil || x.Obj != validationParam { 53 return true 54 } 55 56 default: 57 return true 58 } 59 60 if len(callExpr.Args) == 0 { 61 return true 62 } 63 64 // Given the validation expression, extract the key. 65 key := callExpr.Args[0] 66 switch expr := key.(type) { 67 case *ast.BinaryExpr: 68 // If the argument is a binary expression, take the first expression. 69 // (e.g. c.Validation.Required(myName != "")) 70 key = expr.X 71 case *ast.UnaryExpr: 72 // If the argument is a unary expression, drill in. 73 // (e.g. c.Validation.Required(!myBool) 74 key = expr.X 75 case *ast.BasicLit: 76 // If it's a literal, skip it. 77 return true 78 } 79 80 if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid { 81 lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("") 82 } else { 83 utils.Logger.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", fname, 84 "line", fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String()) 85 } 86 return true 87 }) 88 89 return lineKeys 90 } 91 92 // Check to see if there is a *revel.Validation as an argument. 93 func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object { 94 for _, field := range funcDecl.Type.Params.List { 95 starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation 96 if !ok { 97 continue 98 } 99 100 selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation 101 if !ok { 102 continue 103 } 104 105 xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev 106 if !ok { 107 continue 108 } 109 110 if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == model.RevelImportPath { 111 return field.Names[0].Obj 112 } 113 } 114 return nil 115 }