github.com/mlmmr/revel-cmd@v0.21.2-0.20191112133115-68d8795776dd/model/source_info.go (about)

     1  package model
     2  
     3  // SourceInfo is the top-level struct containing all extracted information
     4  // about the app source code, used to generate main.go.
     5  import (
     6  	"github.com/mlmmr/revel-cmd/utils"
     7  	"path/filepath"
     8  	"strings"
     9  	"unicode"
    10  )
    11  
    12  type SourceInfo struct {
    13  	// StructSpecs lists type info for all structs found under the code paths.
    14  	// They may be queried to determine which ones (transitively) embed certain types.
    15  	StructSpecs []*TypeInfo
    16  	// ValidationKeys provides a two-level lookup.  The keys are:
    17  	// 1. The fully-qualified function name,
    18  	//    e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
    19  	// 2. Within that func's file, the line number of the (overall) expression statement.
    20  	//    e.g. the line returned from runtime.Caller()
    21  	// The result of the lookup the name of variable being validated.
    22  	ValidationKeys map[string]map[int]string
    23  	// A list of import paths.
    24  	// Revel notices files with an init() function and imports that package.
    25  	InitImportPaths []string
    26  
    27  	// controllerSpecs lists type info for all structs found under
    28  	// app/controllers/... that embed (directly or indirectly) revel.Controller
    29  	controllerSpecs []*TypeInfo
    30  	// testSuites list the types that constitute the set of application tests.
    31  	testSuites []*TypeInfo
    32  }
    33  
    34  // TypesThatEmbed returns all types that (directly or indirectly) embed the
    35  // target type, which must be a fully qualified type name,
    36  // e.g. "github.com/revel/revel.Controller"
    37  func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
    38  	// Do a search in the "embedded type graph", starting with the target type.
    39  	var (
    40  		nodeQueue = []string{targetType}
    41  		processed []string
    42  	)
    43  	for len(nodeQueue) > 0 {
    44  		typeSimpleName := nodeQueue[0]
    45  		nodeQueue = nodeQueue[1:]
    46  		processed = append(processed, typeSimpleName)
    47  
    48  		// Look through all known structs.
    49  		for _, spec := range s.StructSpecs {
    50  			// If this one has been processed or is already in nodeQueue, then skip it.
    51  			if utils.ContainsString(processed, spec.String()) ||
    52  				utils.ContainsString(nodeQueue, spec.String()) {
    53  				continue
    54  			}
    55  
    56  			// Look through the embedded types to see if the current type is among them.
    57  			for _, embeddedType := range spec.EmbeddedTypes {
    58  
    59  				// If so, add this type's simple name to the nodeQueue, and its spec to
    60  				// the filtered list.
    61  				if typeSimpleName == embeddedType.String() {
    62  					nodeQueue = append(nodeQueue, spec.String())
    63  					filtered = append(filtered, spec)
    64  					break
    65  				}
    66  			}
    67  		}
    68  	}
    69  
    70  	// Strip out any specifications that contain a lower case
    71  	for exit := false; !exit; exit = true {
    72  		for i, filteredItem := range filtered {
    73  			if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
    74  				utils.Logger.Info("Debug: Skipping adding spec for unexported type",
    75  					"type", filteredItem.StructName,
    76  					"package", filteredItem.ImportPath)
    77  				filtered = append(filtered[:i], filtered[i+1:]...)
    78  				exit = false
    79  				break
    80  			}
    81  		}
    82  	}
    83  
    84  	// Check for any missed types that where from expected packages
    85  	for _, spec := range s.StructSpecs {
    86  		if spec.PackageName == packageFilter {
    87  			found := false
    88  			unfoundNames := ""
    89  			for _, filteredItem := range filtered {
    90  				if filteredItem.StructName == spec.StructName {
    91  					found = true
    92  					break
    93  				} else {
    94  					unfoundNames += filteredItem.StructName + ","
    95  				}
    96  			}
    97  
    98  			// Report non controller structures in controller folder.
    99  			if !found && !strings.HasPrefix(spec.StructName, "Test") {
   100  				utils.Logger.Warn("Type found in package: "+packageFilter+
   101  					", but did not embed from: "+filepath.Base(targetType),
   102  					"name", spec.StructName, "importpath", spec.ImportPath, "foundstructures", unfoundNames)
   103  			}
   104  		}
   105  	}
   106  	return
   107  }
   108  
   109  // ControllerSpecs returns the all the controllers that embeds
   110  // `revel.Controller`
   111  func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
   112  	if s.controllerSpecs == nil {
   113  		s.controllerSpecs = s.TypesThatEmbed(RevelImportPath+".Controller", "controllers")
   114  	}
   115  	return s.controllerSpecs
   116  }
   117  
   118  // TestSuites returns the all the Application tests that embeds
   119  // `testing.TestSuite`
   120  func (s *SourceInfo) TestSuites() []*TypeInfo {
   121  	if s.testSuites == nil {
   122  		s.testSuites = s.TypesThatEmbed(RevelImportPath+"/testing.TestSuite", "testsuite")
   123  	}
   124  	return s.testSuites
   125  }