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  }