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  }