moq/internal/registry/method_scope.go
Suhas Karanth fe0d4f3360
Fix var name generation to avoid conflict (#145)
When the type and the package name is the same for an anonymous
parameter (ex: time.Time), and there are more than 1 such parameters,
the generated name for both was the same. And the generated code would
not be valid.

Fix the bug by ensuring the parameter name does not conflict with
package imports first before checking against other parameter names.
2021-02-14 13:10:16 +00:00

136 lines
3.4 KiB
Go

package registry
import (
"go/types"
"strconv"
)
// MethodScope is the sub-registry for allocating variables present in
// the method scope.
//
// It should be created using a registry instance.
type MethodScope struct {
registry *Registry
moqPkgPath string
vars []*Var
conflicted map[string]bool
}
// AddVar allocates a variable instance and adds it to the method scope.
//
// Variables names are generated if required and are ensured to be
// without conflict with other variables and imported packages. It also
// adds the relevant imports to the registry for each added variable.
func (m *MethodScope) AddVar(vr *types.Var, suffix string) *Var {
imports := make(map[string]*Package)
m.populateImports(vr.Type(), imports)
m.resolveImportVarConflicts(imports)
name := varName(vr, suffix)
// Ensure that the var name does not conflict with a package import.
if _, ok := m.registry.searchImport(name); ok {
name += "MoqParam"
}
if _, ok := m.searchVar(name); ok || m.conflicted[name] {
name = m.resolveVarNameConflict(name)
}
v := Var{
vr: vr,
imports: imports,
moqPkgPath: m.moqPkgPath,
Name: name,
}
m.vars = append(m.vars, &v)
return &v
}
func (m *MethodScope) resolveVarNameConflict(suggested string) string {
for n := 1; ; n++ {
_, ok := m.searchVar(suggested + strconv.Itoa(n))
if ok {
continue
}
if n == 1 {
conflict, _ := m.searchVar(suggested)
conflict.Name += "1"
m.conflicted[suggested] = true
n++
}
return suggested + strconv.Itoa(n)
}
}
func (m MethodScope) searchVar(name string) (*Var, bool) {
for _, v := range m.vars {
if v.Name == name {
return v, true
}
}
return nil, false
}
// populateImports extracts all the package imports for a given type
// recursively. The imported packages by a single type can be more than
// one (ex: map[a.Type]b.Type).
func (m MethodScope) populateImports(t types.Type, imports map[string]*Package) {
switch t := t.(type) {
case *types.Named:
if pkg := t.Obj().Pkg(); pkg != nil {
imports[stripVendorPath(pkg.Path())] = m.registry.AddImport(pkg)
}
case *types.Array:
m.populateImports(t.Elem(), imports)
case *types.Slice:
m.populateImports(t.Elem(), imports)
case *types.Signature:
for i := 0; i < t.Params().Len(); i++ {
m.populateImports(t.Params().At(i).Type(), imports)
}
for i := 0; i < t.Results().Len(); i++ {
m.populateImports(t.Results().At(i).Type(), imports)
}
case *types.Map:
m.populateImports(t.Key(), imports)
m.populateImports(t.Elem(), imports)
case *types.Chan:
m.populateImports(t.Elem(), imports)
case *types.Pointer:
m.populateImports(t.Elem(), imports)
case *types.Struct: // anonymous struct
for i := 0; i < t.NumFields(); i++ {
m.populateImports(t.Field(i).Type(), imports)
}
case *types.Interface: // anonymous interface
for i := 0; i < t.NumExplicitMethods(); i++ {
m.populateImports(t.ExplicitMethod(i).Type(), imports)
}
for i := 0; i < t.NumEmbeddeds(); i++ {
m.populateImports(t.EmbeddedType(i), imports)
}
}
}
// resolveImportVarConflicts ensures that all the newly added imports do not
// conflict with any of the existing vars.
func (m MethodScope) resolveImportVarConflicts(imports map[string]*Package) {
// Ensure that all the newly added imports do not conflict with any of the
// existing vars.
for _, imprt := range imports {
if v, ok := m.searchVar(imprt.Qualifier()); ok {
v.Name += "MoqParam"
}
}
}