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  }