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.
This commit is contained in:
Suhas Karanth 2021-02-14 18:40:16 +05:30 committed by GitHub
parent 9a74351eb1
commit fe0d4f3360
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 109 additions and 96 deletions

View File

@ -23,51 +23,18 @@ type MethodScope struct {
// without conflict with other variables and imported packages. It also // without conflict with other variables and imported packages. It also
// adds the relevant imports to the registry for each added variable. // adds the relevant imports to the registry for each added variable.
func (m *MethodScope) AddVar(vr *types.Var, suffix string) *Var { func (m *MethodScope) AddVar(vr *types.Var, suffix string) *Var {
name := vr.Name()
if name == "" || name == "_" {
name = generateVarName(vr.Type())
}
name += suffix
switch name {
case "mock", "callInfo", "break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for",
"import", "return", "var":
name += "MoqParam"
}
if _, ok := m.searchVar(name); ok || m.conflicted[name] {
return m.addDisambiguatedVar(vr, name)
}
return m.addVar(vr, name)
}
func (m *MethodScope) addDisambiguatedVar(vr *types.Var, suggested string) *Var {
n := 1
for {
// Keep incrementing the suffix until we find a name which is unused.
if _, ok := m.searchVar(suggested + strconv.Itoa(n)); !ok {
break
}
n++
}
name := suggested + strconv.Itoa(n)
if n == 1 {
conflict, _ := m.searchVar(suggested)
conflict.Name += "1"
name = suggested + "2"
m.conflicted[suggested] = true
}
return m.addVar(vr, name)
}
func (m *MethodScope) addVar(vr *types.Var, name string) *Var {
imports := make(map[string]*Package) imports := make(map[string]*Package)
m.populateImports(vr.Type(), imports) 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{ v := Var{
vr: vr, vr: vr,
@ -76,10 +43,26 @@ func (m *MethodScope) addVar(vr *types.Var, name string) *Var {
Name: name, Name: name,
} }
m.vars = append(m.vars, &v) m.vars = append(m.vars, &v)
m.resolveImportVarConflicts(&v)
return &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) { func (m MethodScope) searchVar(name string) (*Var, bool) {
for _, v := range m.vars { for _, v := range m.vars {
if v.Name == name { if v.Name == name {
@ -139,15 +122,12 @@ func (m MethodScope) populateImports(t types.Type, imports map[string]*Package)
} }
} }
func (m MethodScope) resolveImportVarConflicts(v *Var) { // resolveImportVarConflicts ensures that all the newly added imports do not
// Ensure that the newly added var does not conflict with a package import // conflict with any of the existing vars.
// which was added earlier. func (m MethodScope) resolveImportVarConflicts(imports map[string]*Package) {
if _, ok := m.registry.searchImport(v.Name); ok {
v.Name += "MoqParam"
}
// Ensure that all the newly added imports do not conflict with any of the // Ensure that all the newly added imports do not conflict with any of the
// existing vars. // existing vars.
for _, imprt := range v.imports { for _, imprt := range imports {
if v, ok := m.searchVar(imprt.Qualifier()); ok { if v, ok := m.searchVar(imprt.Qualifier()); ok {
v.Name += "MoqParam" v.Name += "MoqParam"
} }

View File

@ -38,7 +38,25 @@ func (v Var) packageQualifier(pkg *types.Package) string {
return v.imports[path].Qualifier() return v.imports[path].Qualifier()
} }
// generateVarName generates a name for the variable using the type func varName(vr *types.Var, suffix string) string {
name := vr.Name()
if name != "" && name != "_" {
return name + suffix
}
name = varNameForType(vr.Type()) + suffix
switch name {
case "mock", "callInfo", "break", "default", "func", "interface", "select", "case", "defer", "go", "map", "struct",
"chan", "else", "goto", "package", "switch", "const", "fallthrough", "if", "range", "type", "continue", "for",
"import", "return", "var":
name += "MoqParam"
}
return name
}
// varNameForType generates a name for the variable using the type
// information. // information.
// //
// Examples: // Examples:
@ -49,12 +67,12 @@ func (v Var) packageQualifier(pkg *types.Package) string {
// - map[string]int -> stringToInt // - map[string]int -> stringToInt
// - error -> err // - error -> err
// - a.MyType -> myType // - a.MyType -> myType
func generateVarName(t types.Type) string { func varNameForType(t types.Type) string {
nestedType := func(t types.Type) string { nestedType := func(t types.Type) string {
if t, ok := t.(*types.Basic); ok { if t, ok := t.(*types.Basic); ok {
return deCapitalise(t.String()) return deCapitalise(t.String())
} }
return generateVarName(t) return varNameForType(t)
} }
switch t := t.(type) { switch t := t.(type) {
@ -83,7 +101,7 @@ func generateVarName(t types.Type) string {
return "val" return "val"
case *types.Pointer: case *types.Pointer:
return generateVarName(t.Elem()) return varNameForType(t.Elem())
case *types.Signature: case *types.Signature:
return "fn" return "fn"

View File

@ -1,5 +1,7 @@
package paramconflict package paramconflict
import "time"
type Interface interface { type Interface interface {
Method(string, bool, string, bool, int, int32, int64, float32, float64) Method(string, bool, string, bool, int, int32, int64, float32, float64, time.Time, time.Time)
} }

View File

@ -5,6 +5,7 @@ package paramconflict
import ( import (
"sync" "sync"
"time"
) )
// Ensure, that InterfaceMock does implement Interface. // Ensure, that InterfaceMock does implement Interface.
@ -17,7 +18,7 @@ var _ Interface = &InterfaceMock{}
// //
// // make and configure a mocked Interface // // make and configure a mocked Interface
// mockedInterface := &InterfaceMock{ // mockedInterface := &InterfaceMock{
// MethodFunc: func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) { // MethodFunc: func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64, timeMoqParam1 time.Time, timeMoqParam2 time.Time) {
// panic("mock out the Method method") // panic("mock out the Method method")
// }, // },
// } // }
@ -28,7 +29,7 @@ var _ Interface = &InterfaceMock{}
// } // }
type InterfaceMock struct { type InterfaceMock struct {
// MethodFunc mocks the Method method. // MethodFunc mocks the Method method.
MethodFunc func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) MethodFunc func(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64, timeMoqParam1 time.Time, timeMoqParam2 time.Time)
// calls tracks calls to the methods. // calls tracks calls to the methods.
calls struct { calls struct {
@ -52,13 +53,17 @@ type InterfaceMock struct {
F1 float32 F1 float32
// F2 is the f2 argument value. // F2 is the f2 argument value.
F2 float64 F2 float64
// TimeMoqParam1 is the timeMoqParam1 argument value.
TimeMoqParam1 time.Time
// TimeMoqParam2 is the timeMoqParam2 argument value.
TimeMoqParam2 time.Time
} }
} }
lockMethod sync.RWMutex lockMethod sync.RWMutex
} }
// Method calls MethodFunc. // Method calls MethodFunc.
func (mock *InterfaceMock) Method(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64) { func (mock *InterfaceMock) Method(s1 string, b1 bool, s2 string, b2 bool, n1 int, n2 int32, n3 int64, f1 float32, f2 float64, timeMoqParam1 time.Time, timeMoqParam2 time.Time) {
if mock.MethodFunc == nil { if mock.MethodFunc == nil {
panic("InterfaceMock.MethodFunc: method is nil but Interface.Method was just called") panic("InterfaceMock.MethodFunc: method is nil but Interface.Method was just called")
} }
@ -72,6 +77,8 @@ func (mock *InterfaceMock) Method(s1 string, b1 bool, s2 string, b2 bool, n1 int
N3 int64 N3 int64
F1 float32 F1 float32
F2 float64 F2 float64
TimeMoqParam1 time.Time
TimeMoqParam2 time.Time
}{ }{
S1: s1, S1: s1,
B1: b1, B1: b1,
@ -82,11 +89,13 @@ func (mock *InterfaceMock) Method(s1 string, b1 bool, s2 string, b2 bool, n1 int
N3: n3, N3: n3,
F1: f1, F1: f1,
F2: f2, F2: f2,
TimeMoqParam1: timeMoqParam1,
TimeMoqParam2: timeMoqParam2,
} }
mock.lockMethod.Lock() mock.lockMethod.Lock()
mock.calls.Method = append(mock.calls.Method, callInfo) mock.calls.Method = append(mock.calls.Method, callInfo)
mock.lockMethod.Unlock() mock.lockMethod.Unlock()
mock.MethodFunc(s1, b1, s2, b2, n1, n2, n3, f1, f2) mock.MethodFunc(s1, b1, s2, b2, n1, n2, n3, f1, f2, timeMoqParam1, timeMoqParam2)
} }
// MethodCalls gets all the calls that were made to Method. // MethodCalls gets all the calls that were made to Method.
@ -102,6 +111,8 @@ func (mock *InterfaceMock) MethodCalls() []struct {
N3 int64 N3 int64
F1 float32 F1 float32
F2 float64 F2 float64
TimeMoqParam1 time.Time
TimeMoqParam2 time.Time
} { } {
var calls []struct { var calls []struct {
S1 string S1 string
@ -113,6 +124,8 @@ func (mock *InterfaceMock) MethodCalls() []struct {
N3 int64 N3 int64
F1 float32 F1 float32
F2 float64 F2 float64
TimeMoqParam1 time.Time
TimeMoqParam2 time.Time
} }
mock.lockMethod.RLock() mock.lockMethod.RLock()
calls = mock.calls.Method calls = mock.calls.Method