diff --git a/README.md b/README.md index 0abf57f..71dbdb9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ At the moment Kelpie is very much in development, and there are missing features The following is a list of known-outstanding features and known issues: -- [ ] For some reason Kelpie is generating multiple mocks when specifying built-in interfaces. See the [Mock Generation](#mock-generation) section for an example. +- [ ] Add support for embedded interfaces. ## Quickstart @@ -96,7 +96,6 @@ Kelpie mock generation starting - preparing to add some magic to your code-base! Parsing package 'io' for interfaces to mock. - Generating a mock for 'Reader'. - - Generating a mock for 'Reader'. Parsing package 'github.com/adamconnelly/kelpie/examples' for interfaces to mock. - Generating a mock for 'Maths'. diff --git a/examples/external_types_test.go b/examples/external_types_test.go index 3446564..735eac7 100644 --- a/examples/external_types_test.go +++ b/examples/external_types_test.go @@ -3,9 +3,10 @@ package examples import ( "testing" + "github.com/stretchr/testify/suite" + "github.com/adamconnelly/kelpie" "github.com/adamconnelly/kelpie/examples/mocks/reader" - "github.com/stretchr/testify/suite" ) type ExternalTypesTests struct { diff --git a/maps/maps.go b/maps/maps.go index f3379ab..c31c3d5 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -12,3 +12,15 @@ func Keys[TKey comparable, TValue any](m map[TKey]TValue) []TKey { return keys } + +// Values returns the values in the map. +func Values[TKey comparable, TValue any](m map[TKey]TValue) []TValue { + values := make([]TValue, len(m)) + index := 0 + for _, v := range m { + values[index] = v + index++ + } + + return values +} diff --git a/parser/parser.go b/parser/parser.go index 29a6727..536b11c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -13,6 +13,7 @@ import ( "github.com/pkg/errors" "golang.org/x/tools/go/packages" + "github.com/adamconnelly/kelpie/maps" "github.com/adamconnelly/kelpie/slices" ) @@ -117,8 +118,6 @@ func (f *IncludingInterfaceFilter) Include(name string) bool { // Parse parses the source contained in the reader. func Parse(packageName string, directory string, filter InterfaceFilter) (*ParsedPackage, error) { - var interfaces []MockedInterface - pkgs, err := packages.Load(&packages.Config{ Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedSyntax | packages.NeedTypesInfo | packages.NeedFiles, Dir: directory, @@ -129,6 +128,7 @@ func Parse(packageName string, directory string, filter InterfaceFilter) (*Parse } var packageDirectory string + interfaces := map[string]MockedInterface{} for _, p := range pkgs { if len(p.Syntax) > 0 && len(p.GoFiles) > 0 { @@ -144,11 +144,14 @@ func Parse(packageName string, directory string, filter InterfaceFilter) (*Parse if t.Name.IsExported() { if interfaceType, ok := t.Type.(*ast.InterfaceType); ok { if filter.Include(t.Name.Name) { - interfaces = append(interfaces, parseInterface(t.Name.Name, t.Name.Name, interfaceType, p, fileNode.Imports)) + i := parseInterface(t.Name.Name, t.Name.Name, interfaceType, p, fileNode.Imports) + interfaces[i.FullName] = i } } else if structType, ok := t.Type.(*ast.StructType); ok { for _, f := range structType.Fields.List { - interfaces = append(interfaces, parseStructField(t, f, p, fileNode.Imports, filter)...) + for _, i := range parseStructField(t, f, p, fileNode.Imports, filter) { + interfaces[i.FullName] = i + } } } } @@ -164,7 +167,7 @@ func Parse(packageName string, directory string, filter InterfaceFilter) (*Parse } } - return &ParsedPackage{PackageDirectory: packageDirectory, Mocks: interfaces}, nil + return &ParsedPackage{PackageDirectory: packageDirectory, Mocks: maps.Values(interfaces)}, nil } func parseStructField(structNode *ast.TypeSpec, field *ast.Field, pkg *packages.Package, importSpecs []*ast.ImportSpec, filter InterfaceFilter) []MockedInterface { diff --git a/parser/parser_test.go b/parser/parser_test.go index e9ba9b8..e4c9d19 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -689,6 +689,21 @@ type DoubleNested struct { t.Len(DoSomethingElse.Results, 2) } +func (t *ParserTests) Test_Parse_CanParseStdlibInterfaces() { + // Arrange + t.interfaceFilter.Setup(interfacefilter.Include(kelpie.Any[string]()).Return(false)) + t.interfaceFilter.Setup(interfacefilter.Include("Reader").Return(true)) + + // Act + result, err := parser.Parse("io", ".", t.interfaceFilter.Instance()) + + // Assert + t.NoError(err) + t.Len(result.Mocks, 1) + t.Len(result.Mocks[0].Methods, 1) + t.Equal("Read", result.Mocks[0].Methods[0].Name) +} + func (t *ParserTests) Test_MockedInterface_AnyMethodsHaveParameters_ReturnsFalseIfNoMethodsHaveParameters() { // Arrange mockedInterface := parser.MockedInterface{