2ae606f132
* Internal registry for disambiguated imports, vars - Move functionality in the moq package partially into internal/{registry,template}. - Leverage registry to assign unique package and variable/method parameter names. Use import aliases if present in interface source package. BREAKING CHANGE: When the interface definition does not mention the parameter names, the field names in call info anonymous struct will be different. The new field names are generated using the type info (string -> s, int -> n, chan int -> intCh, []MyType -> myTypes, map[string]int -> stringToInt etc.). For example, for a string parameter previously if the field name was 'In1', the new field could be 'S' or 'S1' (depends on number of string method parameters). * Refactor golden file tests to be table-driven * Fix sync pkg alias handling for moq generation * Improve, add tests (increase coverage) * Use $.Foo in template, avoid declaring variables $ is set to the data argument passed to Execute, that is, to the starting value of dot. Variables were declared to be able to refer to the parent context. * Consistent template field formatting * Use tabs in generated Godoc comments' example code * Minor simplification * go generate * Fix conflict for generated param name of pointer type Excellent work by @sudo-suhas.
191 lines
4.7 KiB
Go
191 lines
4.7 KiB
Go
package registry
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"go/types"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
// Registry encapsulates types information for the source and mock
|
|
// destination package. For the mock package, it tracks the list of
|
|
// imports and ensures there are no conflicts in the imported package
|
|
// qualifiers.
|
|
type Registry struct {
|
|
srcPkg *packages.Package
|
|
moqPkgPath string
|
|
aliases map[string]string
|
|
imports map[string]*Package
|
|
}
|
|
|
|
// New loads the source package info and returns a new instance of
|
|
// Registry.
|
|
func New(srcDir, moqPkg string) (*Registry, error) {
|
|
srcPkg, err := pkgInfoFromPath(
|
|
srcDir, packages.NeedName|packages.NeedSyntax|packages.NeedTypes|packages.NeedTypesInfo,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't load source package: %s", err)
|
|
}
|
|
|
|
return &Registry{
|
|
srcPkg: srcPkg,
|
|
moqPkgPath: findPkgPath(moqPkg, srcPkg),
|
|
aliases: parseImportsAliases(srcPkg),
|
|
imports: make(map[string]*Package),
|
|
}, nil
|
|
}
|
|
|
|
// SrcPkg returns the types info for the source package.
|
|
func (r Registry) SrcPkg() *types.Package {
|
|
return r.srcPkg.Types
|
|
}
|
|
|
|
// SrcPkgName returns the name of the source package.
|
|
func (r Registry) SrcPkgName() string {
|
|
return r.srcPkg.Name
|
|
}
|
|
|
|
// LookupInterface returns the underlying interface definition of the
|
|
// given interface name.
|
|
func (r Registry) LookupInterface(name string) (*types.Interface, error) {
|
|
obj := r.SrcPkg().Scope().Lookup(name)
|
|
if obj == nil {
|
|
return nil, fmt.Errorf("interface not found: %s", name)
|
|
}
|
|
|
|
if !types.IsInterface(obj.Type()) {
|
|
return nil, fmt.Errorf("%s (%s) is not an interface", name, obj.Type())
|
|
}
|
|
|
|
return obj.Type().Underlying().(*types.Interface).Complete(), nil
|
|
}
|
|
|
|
// MethodScope returns a new MethodScope.
|
|
func (r *Registry) MethodScope() *MethodScope {
|
|
return &MethodScope{
|
|
registry: r,
|
|
moqPkgPath: r.moqPkgPath,
|
|
conflicted: map[string]bool{},
|
|
}
|
|
}
|
|
|
|
// AddImport adds the given package to the set of imports. It generates a
|
|
// suitable alias if there are any conflicts with previously imported
|
|
// packages.
|
|
func (r *Registry) AddImport(pkg *types.Package) *Package {
|
|
path := stripVendorPath(pkg.Path())
|
|
if path == r.moqPkgPath {
|
|
return nil
|
|
}
|
|
|
|
if imprt, ok := r.imports[path]; ok {
|
|
return imprt
|
|
}
|
|
|
|
imprt := Package{pkg: pkg, Alias: r.aliases[path]}
|
|
|
|
if conflict, ok := r.searchImport(imprt.Qualifier()); ok {
|
|
resolveImportConflict(&imprt, conflict, 0)
|
|
}
|
|
|
|
r.imports[path] = &imprt
|
|
return &imprt
|
|
}
|
|
|
|
// Imports returns the list of imported packages. The list is sorted by
|
|
// path.
|
|
func (r Registry) Imports() []*Package {
|
|
imports := make([]*Package, 0, len(r.imports))
|
|
for _, imprt := range r.imports {
|
|
imports = append(imports, imprt)
|
|
}
|
|
sort.Slice(imports, func(i, j int) bool {
|
|
return imports[i].Path() < imports[j].Path()
|
|
})
|
|
return imports
|
|
}
|
|
|
|
func (r Registry) searchImport(name string) (*Package, bool) {
|
|
for _, imprt := range r.imports {
|
|
if imprt.Qualifier() == name {
|
|
return imprt, true
|
|
}
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func pkgInfoFromPath(srcDir string, mode packages.LoadMode) (*packages.Package, error) {
|
|
pkgs, err := packages.Load(&packages.Config{
|
|
Mode: mode,
|
|
Dir: srcDir,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(pkgs) == 0 {
|
|
return nil, errors.New("package not found")
|
|
}
|
|
if len(pkgs) > 1 {
|
|
return nil, errors.New("found more than one package")
|
|
}
|
|
if errs := pkgs[0].Errors; len(errs) != 0 {
|
|
if len(errs) == 1 {
|
|
return nil, errs[0]
|
|
}
|
|
return nil, fmt.Errorf("%s (and %d more errors)", errs[0], len(errs)-1)
|
|
}
|
|
return pkgs[0], nil
|
|
}
|
|
|
|
func findPkgPath(pkgInputVal string, srcPkg *packages.Package) string {
|
|
if pkgInputVal == "" {
|
|
return srcPkg.PkgPath
|
|
}
|
|
if pkgInDir(srcPkg.PkgPath, pkgInputVal) {
|
|
return srcPkg.PkgPath
|
|
}
|
|
subdirectoryPath := filepath.Join(srcPkg.PkgPath, pkgInputVal)
|
|
if pkgInDir(subdirectoryPath, pkgInputVal) {
|
|
return subdirectoryPath
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func pkgInDir(pkgName, dir string) bool {
|
|
currentPkg, err := pkgInfoFromPath(dir, packages.NeedName)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return currentPkg.Name == pkgName || currentPkg.Name+"_test" == pkgName
|
|
}
|
|
|
|
func parseImportsAliases(pkg *packages.Package) map[string]string {
|
|
aliases := make(map[string]string)
|
|
for _, syntax := range pkg.Syntax {
|
|
for _, imprt := range syntax.Imports {
|
|
if imprt.Name != nil && imprt.Name.Name != "." {
|
|
aliases[strings.Trim(imprt.Path.Value, `"`)] = imprt.Name.Name
|
|
}
|
|
}
|
|
}
|
|
return aliases
|
|
}
|
|
|
|
// resolveImportConflict generates and assigns a unique alias for
|
|
// packages with conflicting qualifiers.
|
|
func resolveImportConflict(a, b *Package, lvl int) {
|
|
u1, u2 := a.uniqueName(lvl), b.uniqueName(lvl)
|
|
if u1 != u2 {
|
|
a.Alias, b.Alias = u1, u2
|
|
return
|
|
}
|
|
|
|
resolveImportConflict(a, b, lvl+1)
|
|
}
|