diff --git a/go.sum b/go.sum index 21dbce53..0549f947 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/mockgen/internal/tests/import_embedded_interface/bugreport.go b/mockgen/internal/tests/import_embedded_interface/bugreport.go new file mode 100644 index 00000000..c6d73f0f --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/bugreport.go @@ -0,0 +1,35 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//go:generate mockgen -destination bugreport_mock.go -package bugreport -source=bugreport.go + +package bugreport + +import ( + "log" + + "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/ersatz" + "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/faux" +) + +// Source is an interface w/ an embedded foreign interface +type Source interface { + ersatz.Embedded + faux.Foreign +} + +func CallForeignMethod(s Source) { + log.Println(s.Ersatz()) + log.Println(s.OtherErsatz()) +} diff --git a/mockgen/internal/tests/import_embedded_interface/bugreport_mock.go b/mockgen/internal/tests/import_embedded_interface/bugreport_mock.go new file mode 100644 index 00000000..c618497a --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/bugreport_mock.go @@ -0,0 +1,63 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: bugreport.go + +// Package bugreport is a generated GoMock package. +package bugreport + +import ( + gomock "github.com/golang/mock/gomock" + ersatz "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/ersatz" + ersatz0 "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/other/ersatz" + reflect "reflect" +) + +// MockSource is a mock of Source interface +type MockSource struct { + ctrl *gomock.Controller + recorder *MockSourceMockRecorder +} + +// MockSourceMockRecorder is the mock recorder for MockSource +type MockSourceMockRecorder struct { + mock *MockSource +} + +// NewMockSource creates a new mock instance +func NewMockSource(ctrl *gomock.Controller) *MockSource { + mock := &MockSource{ctrl: ctrl} + mock.recorder = &MockSourceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSource) EXPECT() *MockSourceMockRecorder { + return m.recorder +} + +// Ersatz mocks base method +func (m *MockSource) Ersatz() ersatz.Return { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Ersatz") + ret0, _ := ret[0].(ersatz.Return) + return ret0 +} + +// Ersatz indicates an expected call of Ersatz +func (mr *MockSourceMockRecorder) Ersatz() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ersatz", reflect.TypeOf((*MockSource)(nil).Ersatz)) +} + +// OtherErsatz mocks base method +func (m *MockSource) OtherErsatz() ersatz0.Return { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OtherErsatz") + ret0, _ := ret[0].(ersatz0.Return) + return ret0 +} + +// OtherErsatz indicates an expected call of OtherErsatz +func (mr *MockSourceMockRecorder) OtherErsatz() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OtherErsatz", reflect.TypeOf((*MockSource)(nil).OtherErsatz)) +} diff --git a/mockgen/internal/tests/import_embedded_interface/bugreport_test.go b/mockgen/internal/tests/import_embedded_interface/bugreport_test.go new file mode 100644 index 00000000..5915cbe1 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/bugreport_test.go @@ -0,0 +1,31 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package bugreport + +import ( + "testing" + + "github.com/golang/mock/gomock" +) + +// TestValidInterface assesses whether or not the generated mock is valid +func TestValidInterface(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + s := NewMockSource(ctrl) + s.EXPECT().Ersatz().Return("") + s.EXPECT().OtherErsatz().Return("") + CallForeignMethod(s) +} diff --git a/mockgen/internal/tests/import_embedded_interface/ersatz/ersatz.go b/mockgen/internal/tests/import_embedded_interface/ersatz/ersatz.go new file mode 100644 index 00000000..0d2c7815 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/ersatz/ersatz.go @@ -0,0 +1,20 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ersatz + +type Embedded interface { + Ersatz() Return +} + +type Return interface{} diff --git a/mockgen/internal/tests/import_embedded_interface/faux/conflict.go b/mockgen/internal/tests/import_embedded_interface/faux/conflict.go new file mode 100644 index 00000000..27803dd6 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/faux/conflict.go @@ -0,0 +1,20 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package faux + +import "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/other/log" + +func Conflict1() { + log.Foo() +} diff --git a/mockgen/internal/tests/import_embedded_interface/faux/faux.go b/mockgen/internal/tests/import_embedded_interface/faux/faux.go new file mode 100644 index 00000000..e1d66988 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/faux/faux.go @@ -0,0 +1,28 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package faux + +import ( + "log" + + "github.com/golang/mock/mockgen/internal/tests/import_embedded_interface/other/ersatz" +) + +type Foreign interface { + ersatz.Embedded +} + +func Conflict0() { + log.Println() +} diff --git a/mockgen/internal/tests/import_embedded_interface/net.go b/mockgen/internal/tests/import_embedded_interface/net.go new file mode 100644 index 00000000..38fbd039 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/net.go @@ -0,0 +1,25 @@ +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//go:generate mockgen -destination net_mock.go -package bugreport -source=net.go +package bugreport + +import "net/http" + +type Net interface { + http.ResponseWriter +} + +func CallResponseWriterMethods(n Net) { + n.WriteHeader(10) +} diff --git a/mockgen/internal/tests/import_embedded_interface/net_mock.go b/mockgen/internal/tests/import_embedded_interface/net_mock.go new file mode 100644 index 00000000..50ec11ec --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/net_mock.go @@ -0,0 +1,75 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: net.go + +// Package bugreport is a generated GoMock package. +package bugreport + +import ( + gomock "github.com/golang/mock/gomock" + http "net/http" + reflect "reflect" +) + +// MockNet is a mock of Net interface +type MockNet struct { + ctrl *gomock.Controller + recorder *MockNetMockRecorder +} + +// MockNetMockRecorder is the mock recorder for MockNet +type MockNetMockRecorder struct { + mock *MockNet +} + +// NewMockNet creates a new mock instance +func NewMockNet(ctrl *gomock.Controller) *MockNet { + mock := &MockNet{ctrl: ctrl} + mock.recorder = &MockNetMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockNet) EXPECT() *MockNetMockRecorder { + return m.recorder +} + +// Header mocks base method +func (m *MockNet) Header() http.Header { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(http.Header) + return ret0 +} + +// Header indicates an expected call of Header +func (mr *MockNetMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockNet)(nil).Header)) +} + +// Write mocks base method +func (m *MockNet) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write +func (mr *MockNetMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockNet)(nil).Write), arg0) +} + +// WriteHeader mocks base method +func (m *MockNet) WriteHeader(statusCode int) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "WriteHeader", statusCode) +} + +// WriteHeader indicates an expected call of WriteHeader +func (mr *MockNetMockRecorder) WriteHeader(statusCode interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteHeader", reflect.TypeOf((*MockNet)(nil).WriteHeader), statusCode) +} diff --git a/mockgen/internal/tests/import_embedded_interface/net_test.go b/mockgen/internal/tests/import_embedded_interface/net_test.go new file mode 100644 index 00000000..381f7de3 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/net_test.go @@ -0,0 +1,30 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package bugreport + +import ( + "testing" + + "github.com/golang/mock/gomock" +) + +// TestValidInterface assesses whether or not the generated mock is valid +func TestValidNetInterface(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + s := NewMockNet(ctrl) + s.EXPECT().WriteHeader(10) + CallResponseWriterMethods(s) +} diff --git a/mockgen/internal/tests/import_embedded_interface/other/ersatz/ersatz.go b/mockgen/internal/tests/import_embedded_interface/other/ersatz/ersatz.go new file mode 100644 index 00000000..9ca63914 --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/other/ersatz/ersatz.go @@ -0,0 +1,20 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ersatz + +type Embedded interface { + OtherErsatz() Return +} + +type Return interface{} diff --git a/mockgen/internal/tests/import_embedded_interface/other/log/log.go b/mockgen/internal/tests/import_embedded_interface/other/log/log.go new file mode 100644 index 00000000..c0d032bf --- /dev/null +++ b/mockgen/internal/tests/import_embedded_interface/other/log/log.go @@ -0,0 +1,16 @@ +// Copyright 2020 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package log + +func Foo() {} diff --git a/mockgen/parse.go b/mockgen/parse.go index f47a3d9a..0ba7f805 100644 --- a/mockgen/parse.go +++ b/mockgen/parse.go @@ -62,7 +62,7 @@ func sourceMode(source string) (*model.Package, error) { p := &fileParser{ fileSet: fs, - imports: make(map[string]string), + imports: make(map[string]importedPackage), importedInterfaces: make(map[string]map[string]*ast.InterfaceType), auxInterfaces: make(map[string]map[string]*ast.InterfaceType), srcDir: srcDir, @@ -79,7 +79,7 @@ func sourceMode(source string) (*model.Package, error) { dotImports[v] = true } else { // TODO: Catch dupes? - p.imports[k] = v + p.imports[k] = importedPkg{path: v} } } } @@ -100,9 +100,39 @@ func sourceMode(source string) (*model.Package, error) { return pkg, nil } +type importedPackage interface { + Path() string + Parser() *fileParser +} + +type importedPkg struct { + path string + parser *fileParser +} + +func (i importedPkg) Path() string { return i.path } +func (i importedPkg) Parser() *fileParser { return i.parser } + +// duplicateImport is a bit of a misnomer. Currently the parser can't +// handle cases of multi-file packages importing different packages +// under the same name. Often these imports would not be problematic, +// so this type lets us defer raising an error unless the package name +// is actually used. +type duplicateImport struct { + name string + duplicates []string +} + +func (d duplicateImport) Error() string { + return fmt.Sprintf("%q is ambigous because of duplicate imports: %v", d.name, d.duplicates) +} + +func (d duplicateImport) Path() string { log.Fatal(d.Error()); return "" } +func (d duplicateImport) Parser() *fileParser { log.Fatal(d.Error()); return nil } + type fileParser struct { fileSet *token.FileSet - imports map[string]string // package name => import path + imports map[string]importedPackage // package name => imported package importedInterfaces map[string]map[string]*ast.InterfaceType // package (or "") => name => interface auxFiles []*ast.File @@ -154,18 +184,18 @@ func (p *fileParser) addAuxInterfacesFromFile(pkg string, file *ast.File) { func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Package, error) { allImports, dotImports := importsOfFile(file) // Don't stomp imports provided by -imports. Those should take precedence. - for pkg, pkgPath := range allImports { + for pkg, pkgI := range allImports { if _, ok := p.imports[pkg]; !ok { - p.imports[pkg] = pkgPath + p.imports[pkg] = pkgI } } // Add imports from auxiliary files, which might be needed for embedded interfaces. // Don't stomp any other imports. for _, f := range p.auxFiles { auxImports, _ := importsOfFile(f) - for pkg, pkgPath := range auxImports { + for pkg, pkgI := range auxImports { if _, ok := p.imports[pkg]; !ok { - p.imports[pkg] = pkgPath + p.imports[pkg] = pkgI } } } @@ -186,31 +216,38 @@ func (p *fileParser) parseFile(importPath string, file *ast.File) (*model.Packag }, nil } -// parsePackage loads package specified by path, parses it and populates -// corresponding imports and importedInterfaces into the fileParser. -func (p *fileParser) parsePackage(path string) error { +// parsePackage loads package specified by path, parses it and returns +// a new fileParser with the parsed imports and interfaces. +func (p *fileParser) parsePackage(path string) (*fileParser, error) { + newP := &fileParser{ + fileSet: token.NewFileSet(), + imports: make(map[string]importedPackage), + importedInterfaces: make(map[string]map[string]*ast.InterfaceType), + auxInterfaces: make(map[string]map[string]*ast.InterfaceType), + srcDir: p.srcDir, + } + var pkgs map[string]*ast.Package - if imp, err := build.Import(path, p.srcDir, build.FindOnly); err != nil { - return err - } else if pkgs, err = parser.ParseDir(p.fileSet, imp.Dir, nil, 0); err != nil { - return err + if imp, err := build.Import(path, newP.srcDir, build.FindOnly); err != nil { + return nil, err + } else if pkgs, err = parser.ParseDir(newP.fileSet, imp.Dir, nil, 0); err != nil { + return nil, err } + for _, pkg := range pkgs { file := ast.MergePackageFiles(pkg, ast.FilterFuncDuplicates|ast.FilterUnassociatedComments|ast.FilterImportDuplicates) - if _, ok := p.importedInterfaces[path]; !ok { - p.importedInterfaces[path] = make(map[string]*ast.InterfaceType) + if _, ok := newP.importedInterfaces[path]; !ok { + newP.importedInterfaces[path] = make(map[string]*ast.InterfaceType) } for ni := range iterInterfaces(file) { - p.importedInterfaces[path][ni.name.Name] = ni.it + newP.importedInterfaces[path][ni.name.Name] = ni.it } imports, _ := importsOfFile(file) - for pkgName, pkgPath := range imports { - if _, ok := p.imports[pkgName]; !ok { - p.imports[pkgName] = pkgPath - } + for pkgName, pkgI := range imports { + newP.imports[pkgName] = pkgI } } - return nil + return newP, nil } func (p *fileParser) parseInterface(name, pkg string, it *ast.InterfaceType) (*model.Interface, error) { @@ -252,21 +289,36 @@ func (p *fileParser) parseInterface(name, pkg string, it *ast.InterfaceType) (*m if !ok { return nil, p.errorf(v.X.Pos(), "unknown package %s", fpkg) } + + var eintf *model.Interface + var err error ei := p.auxInterfaces[fpkg][sel] - if ei == nil { - fpkg = epkg - if _, ok = p.importedInterfaces[epkg]; !ok { - if err := p.parsePackage(epkg); err != nil { - return nil, p.errorf(v.Pos(), "could not parse package %s: %v", fpkg, err) + if ei != nil { + eintf, err = p.parseInterface(sel, fpkg, ei) + if err != nil { + return nil, err + } + } else { + path := epkg.Path() + parser := epkg.Parser() + if parser == nil { + ip, err := p.parsePackage(path) + if err != nil { + return nil, p.errorf(v.Pos(), "could not parse package %s: %v", path, err) + } + parser = ip + p.imports[fpkg] = importedPkg{ + path: epkg.Path(), + parser: parser, } } - if ei = p.importedInterfaces[epkg][sel]; ei == nil { - return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", fpkg, sel) + if ei = parser.importedInterfaces[path][sel]; ei == nil { + return nil, p.errorf(v.Pos(), "unknown embedded interface %s.%s", path, sel) + } + eintf, err = parser.parseInterface(sel, path, ei) + if err != nil { + return nil, err } - } - eintf, err := p.parseInterface(sel, fpkg, ei) - if err != nil { - return nil, err } // Copy the methods. // TODO: apply shadowing rules. @@ -383,7 +435,7 @@ func (p *fileParser) parseType(pkg string, typ ast.Expr) (model.Type, error) { // if so, patch the import w/ the fully qualified import maybeImportedPkg, ok := p.imports[pkg] if ok { - pkg = maybeImportedPkg + pkg = maybeImportedPkg.Path() } // assume type in this package return &model.NamedType{Package: pkg, Type: v.Name}, nil @@ -412,7 +464,7 @@ func (p *fileParser) parseType(pkg string, typ ast.Expr) (model.Type, error) { if !ok { return nil, p.errorf(v.Pos(), "unknown package %q", pkgName) } - return &model.NamedType{Package: pkg, Type: v.Sel.String()}, nil + return &model.NamedType{Package: pkg.Path(), Type: v.Sel.String()}, nil case *ast.StarExpr: t, err := p.parseType(pkg, v.X) if err != nil { @@ -433,7 +485,7 @@ func (p *fileParser) parseType(pkg string, typ ast.Expr) (model.Type, error) { // importsOfFile returns a map of package name to import path // of the imports in file. -func importsOfFile(file *ast.File) (normalImports map[string]string, dotImports []string) { +func importsOfFile(file *ast.File) (normalImports map[string]importedPackage, dotImports []string) { var importPaths []string for _, is := range file.Imports { if is.Name != nil { @@ -443,7 +495,7 @@ func importsOfFile(file *ast.File) (normalImports map[string]string, dotImports importPaths = append(importPaths, importPath) } packagesName := createPackageMap(importPaths) - normalImports = make(map[string]string) + normalImports = make(map[string]importedPackage) dotImports = make([]string, 0) for _, is := range file.Imports { var pkgName string @@ -471,11 +523,22 @@ func importsOfFile(file *ast.File) (normalImports map[string]string, dotImports if pkgName == "." { dotImports = append(dotImports, importPath) } else { - - if _, ok := normalImports[pkgName]; ok { - log.Fatalf("imported package collision: %q imported twice", pkgName) + if pkg, ok := normalImports[pkgName]; ok { + switch p := pkg.(type) { + case duplicateImport: + normalImports[pkgName] = duplicateImport{ + name: p.name, + duplicates: append([]string{importPath}, p.duplicates...), + } + case importedPkg: + normalImports[pkgName] = duplicateImport{ + name: pkgName, + duplicates: []string{p.path, importPath}, + } + } + } else { + normalImports[pkgName] = importedPkg{path: importPath} } - normalImports[pkgName] = importPath } } return diff --git a/mockgen/parse_test.go b/mockgen/parse_test.go index dab22fa5..e555b45e 100644 --- a/mockgen/parse_test.go +++ b/mockgen/parse_test.go @@ -16,7 +16,7 @@ func TestFileParser_ParseFile(t *testing.T) { p := fileParser{ fileSet: fs, - imports: make(map[string]string), + imports: make(map[string]importedPackage), importedInterfaces: make(map[string]map[string]*ast.InterfaceType), } @@ -47,16 +47,16 @@ func TestFileParser_ParsePackage(t *testing.T) { p := fileParser{ fileSet: fs, - imports: make(map[string]string), + imports: make(map[string]importedPackage), importedInterfaces: make(map[string]map[string]*ast.InterfaceType), } - err = p.parsePackage("github.com/golang/mock/mockgen/internal/tests/custom_package_name/greeter") + newP, err := p.parsePackage("github.com/golang/mock/mockgen/internal/tests/custom_package_name/greeter") if err != nil { t.Fatalf("Unexpected error: %v", err) } - checkGreeterImports(t, p.imports) + checkGreeterImports(t, newP.imports) } func TestImportsOfFile(t *testing.T) { @@ -70,14 +70,14 @@ func TestImportsOfFile(t *testing.T) { checkGreeterImports(t, imports) } -func checkGreeterImports(t *testing.T, imports map[string]string) { +func checkGreeterImports(t *testing.T, imports map[string]importedPackage) { // check that imports have stdlib package "fmt" if fmtPackage, ok := imports["fmt"]; !ok { t.Errorf("Expected imports to have key \"fmt\"") } else { expectedFmtPackage := "fmt" - if fmtPackage != expectedFmtPackage { - t.Errorf("Expected fmt key to have value %s but got %s", expectedFmtPackage, fmtPackage) + if fmtPackage.Path() != expectedFmtPackage { + t.Errorf("Expected fmt key to have value %s but got %s", expectedFmtPackage, fmtPackage.Path()) } } @@ -86,8 +86,8 @@ func checkGreeterImports(t *testing.T, imports map[string]string) { t.Errorf("Expected imports to have key \"fmt\"") } else { expectedValidatorPackage := "github.com/golang/mock/mockgen/internal/tests/custom_package_name/validator" - if validatorPackage != expectedValidatorPackage { - t.Errorf("Expected validator key to have value %s but got %s", expectedValidatorPackage, validatorPackage) + if validatorPackage.Path() != expectedValidatorPackage { + t.Errorf("Expected validator key to have value %s but got %s", expectedValidatorPackage, validatorPackage.Path()) } } @@ -96,8 +96,8 @@ func checkGreeterImports(t *testing.T, imports map[string]string) { t.Errorf("Expected imports to have key \"client\"") } else { expectedClientPackage := "github.com/golang/mock/mockgen/internal/tests/custom_package_name/client/v1" - if clientPackage != expectedClientPackage { - t.Errorf("Expected client key to have value %s but got %s", expectedClientPackage, clientPackage) + if clientPackage.Path() != expectedClientPackage { + t.Errorf("Expected client key to have value %s but got %s", expectedClientPackage, clientPackage.Path()) } }