-
-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Internal registry for disambiguated imports, vars (#141)
* 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.
- Loading branch information
1 parent
b052143
commit 2ae606f
Showing
37 changed files
with
2,066 additions
and
671 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
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 { | ||
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) | ||
m.populateImports(vr.Type(), imports) | ||
|
||
v := Var{ | ||
vr: vr, | ||
imports: imports, | ||
moqPkgPath: m.moqPkgPath, | ||
Name: name, | ||
} | ||
m.vars = append(m.vars, &v) | ||
m.resolveImportVarConflicts(&v) | ||
return &v | ||
} | ||
|
||
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) | ||
} | ||
} | ||
} | ||
|
||
func (m MethodScope) resolveImportVarConflicts(v *Var) { | ||
// Ensure that the newly added var does not conflict with a package import | ||
// which was added earlier. | ||
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 | ||
// existing vars. | ||
for _, imprt := range v.imports { | ||
if v, ok := m.searchVar(imprt.Qualifier()); ok { | ||
v.Name += "MoqParam" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package registry | ||
|
||
import ( | ||
"go/types" | ||
"path" | ||
"strings" | ||
) | ||
|
||
// Package represents an imported package. | ||
type Package struct { | ||
pkg *types.Package | ||
|
||
Alias string | ||
} | ||
|
||
// NewPackage creates a new instance of Package. | ||
func NewPackage(pkg *types.Package) *Package { return &Package{pkg: pkg} } | ||
|
||
// Qualifier returns the qualifier which must be used to refer to types | ||
// declared in the package. | ||
func (p *Package) Qualifier() string { | ||
if p == nil { | ||
return "" | ||
} | ||
|
||
if p.Alias != "" { | ||
return p.Alias | ||
} | ||
|
||
return p.pkg.Name() | ||
} | ||
|
||
// Path is the full package import path (without vendor). | ||
func (p *Package) Path() string { | ||
if p == nil { | ||
return "" | ||
} | ||
|
||
return stripVendorPath(p.pkg.Path()) | ||
} | ||
|
||
var replacer = strings.NewReplacer( | ||
"go-", "", | ||
"-go", "", | ||
"-", "", | ||
"_", "", | ||
".", "", | ||
"@", "", | ||
"+", "", | ||
"~", "", | ||
) | ||
|
||
// uniqueName generates a unique name for a package by concatenating | ||
// path components. The generated name is guaranteed to unique with an | ||
// appropriate level because the full package import paths themselves | ||
// are unique. | ||
func (p Package) uniqueName(lvl int) string { | ||
pp := strings.Split(p.Path(), "/") | ||
reverse(pp) | ||
|
||
var name string | ||
for i := 0; i < min(len(pp), lvl+1); i++ { | ||
name = strings.ToLower(replacer.Replace(pp[i])) + name | ||
} | ||
|
||
return name | ||
} | ||
|
||
// stripVendorPath strips the vendor dir prefix from a package path. | ||
// For example we might encounter an absolute path like | ||
// github.com/foo/bar/vendor/github.com/pkg/errors which is resolved | ||
// to github.com/pkg/errors. | ||
func stripVendorPath(p string) string { | ||
parts := strings.Split(p, "/vendor/") | ||
if len(parts) == 1 { | ||
return p | ||
} | ||
return strings.TrimLeft(path.Join(parts[1:]...), "/") | ||
} | ||
|
||
func min(a, b int) int { | ||
if a < b { | ||
return a | ||
} | ||
return b | ||
} | ||
|
||
func reverse(a []string) { | ||
for i := len(a)/2 - 1; i >= 0; i-- { | ||
opp := len(a) - 1 - i | ||
a[i], a[opp] = a[opp], a[i] | ||
} | ||
} |
Oops, something went wrong.