Repository: golang/mock
Pull request #387 (2020-01-18 07:27:26)

Use "." to refer to the current path's package in reflect mode
**Description**

I use `go:generate` in my source code to invoke `mockgen` to generate mocked interfaces.

e.g.

```go
//go:generate mockgen -destination=mocks/foo.pb.go github.com/xxx/xxx/pkg/xxx Foo
```

But it's too annoying that every time I have to write and check the package name (github.com/xxx/xxx/pkg/xxx).

And add the full package name in a source file is kind of duplicated information since *golang* could parse the file path and get the package name.

So I add a feature that can use "." to refer to the current path's package.

e.g.

```go
//go:generate mockgen -destination=mocks/foo.pb.go . Foo
```

Just a single `.`, no more `github.com/xxx/xxx/pkg/xxx`.

**Release Notes**

```
Use "." to refer to the current path's package in reflect mode
```

googlebot: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgolang%2Fmock%2Fpull%2F387) for more info**.

<!-- need_sender_cla -->
googlebot: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here with `@googlebot I signed it!` and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgolang%2Fmock%2Fpull%2F387) for more info**.

<!-- need_sender_cla -->
XSAM: @googlebot I signed it!
googlebot: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgolang%2Fmock%2Fpull%2F387) for more info**.

<!-- ok -->
googlebot: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgolang%2Fmock%2Fpull%2F387) for more info**.

<!-- ok -->
codyoss: Hey @XSAM, thanks for the PR. Is their a corresponding issue that goes with this PR? If not, could you please create one to discuss the feature. 

Patch:
diff --git a/README.md b/README.md
index 9204baf..2a6f000 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,17 @@ contexts too.
 Installation
 ------------
 
-Once you have [installed Go][golang-install], install the `mockgen` tool:
+Once you have [installed Go][golang-install], install the `mockgen` tool.
+
+To get the latest released version use:
 
 ```bash
-go get github.com/golang/mock/mockgen
+GO111MODULE=on go get github.com/golang/mock/mockgen@latest
 ```
 
-_Note: It is recommended to have `GO111MODULE=on` to ensure the correct
-dependencies are used._
+If you use `mockgen` in your CI pipeline, it may be more appropriate to fixate
+on a specific mockgen version.
+
 
 Documentation
 -------------
@@ -48,10 +51,15 @@ that uses reflection to understand interfaces. It is enabled
 by passing two non-flag arguments: an import path, and a
 comma-separated list of symbols.
 
+You can use "." to refer to the current path's package.
+
 Example:
 
 ```bash
 mockgen database/sql/driver Conn,Driver
+
+# Convenient for `go:generate`.
+mockgen . Conn,Driver
 ```
 
 The `mockgen` command is used to generate source code for a mock
diff --git a/go.mod b/go.mod
index a675ec4..edfb6d4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,8 @@
 module github.com/golang/mock
 
-require golang.org/x/tools v0.0.0-20190425150028-36563e24a262
+require (
+	golang.org/x/tools v0.0.0-20190425150028-36563e24a262
+	rsc.io/quote/v3 v3.1.0
+)
 
 go 1.11
diff --git a/go.sum b/go.sum
index 9009852..21dbce5 100644
--- a/go.sum
+++ b/go.sum
@@ -2,6 +2,12 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 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=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=
 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/mockgen/mockgen.go b/mockgen/mockgen.go
index 8126c81..b13885d 100644
--- a/mockgen/mockgen.go
+++ b/mockgen/mockgen.go
@@ -20,6 +20,7 @@ package main
 
 import (
 	"bytes"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"go/build"
@@ -29,6 +30,7 @@ import (
 	"io/ioutil"
 	"log"
 	"os"
+	"os/exec"
 	"path"
 	"path/filepath"
 	"sort"
@@ -53,14 +55,21 @@ var (
 	copyrightFile   = flag.String("copyright_file", "", "Copyright file used to add copyright header")
 
 	debugParser = flag.Bool("debug_parser", false, "Print out parser results only.")
+	version     = flag.Bool("version", false, "Print version.")
 )
 
 func main() {
 	flag.Usage = usage
 	flag.Parse()
 
+	if *version {
+		printVersion()
+		return
+	}
+
 	var pkg *model.Package
 	var err error
+	var packageName string
 	if *source != "" {
 		pkg, err = sourceMode(*source)
 	} else {
@@ -68,7 +77,18 @@ func main() {
 			usage()
 			log.Fatal("Expected exactly two arguments")
 		}
-		pkg, err = reflectMode(flag.Arg(0), strings.Split(flag.Arg(1), ","))
+		packageName = flag.Arg(0)
+		if packageName == "." {
+			dir, err := os.Getwd()
+			if err != nil {
+				log.Fatalf("Get current directory failed: %v", err)
+			}
+			packageName, err = packageNameOfDir(dir)
+			if err != nil {
+				log.Fatalf("Parse package name failed: %v", err)
+			}
+		}
+		pkg, err = reflectMode(packageName, strings.Split(flag.Arg(1), ","))
 	}
 	if err != nil {
 		log.Fatalf("Loading input failed: %v", err)
@@ -122,7 +142,7 @@ func main() {
 	if *source != "" {
 		g.filename = *source
 	} else {
-		g.srcPackage = flag.Arg(0)
+		g.srcPackage = packageName
 		g.srcInterfaces = flag.Arg(1)
 	}
 
@@ -281,7 +301,10 @@ func (g *generator) Generate(pkg *model.Package, outputPkgName string, outputPac
 	g.packageMap = make(map[string]string, len(im))
 	localNames := make(map[string]bool, len(im))
 	for _, pth := range sortedPaths {
-		base := sanitize(path.Base(pth))
+		base, ok := lookupPackageName(pth)
+		if !ok {
+			base = sanitize(path.Base(pth))
+		}
 
 		// Local names for an imported package can usually be the basename of the import path.
 		// A couple of situations don't permit that, such as duplicate local names
@@ -592,3 +615,21 @@ func (g *generator) Output() []byte {
 	}
 	return src
 }
+
+func lookupPackageName(importPath string) (string, bool) {
+	var pkg struct {
+		Name string
+	}
+	b := bytes.NewBuffer(nil)
+	cmd := exec.Command("go", "list", "-json", importPath)
+	cmd.Stdout = b
+	err := cmd.Run()
+	if err != nil {
+		return "", false
+	}
+	err = json.Unmarshal(b.Bytes(), &pkg)
+	if err != nil {
+		return "", false
+	}
+	return pkg.Name, true
+}
diff --git a/mockgen/parse.go b/mockgen/parse.go
index e35d16f..d88f3c9 100644
--- a/mockgen/parse.go
+++ b/mockgen/parse.go
@@ -24,6 +24,7 @@ import (
 	"go/build"
 	"go/parser"
 	"go/token"
+	"io/ioutil"
 	"log"
 	"path"
 	"path/filepath"
@@ -48,19 +49,10 @@ func sourceMode(source string) (*model.Package, error) {
 		return nil, fmt.Errorf("failed getting source directory: %v", err)
 	}
 
-	cfg := &packages.Config{Mode: packages.LoadFiles, Tests: true, Dir: srcDir}
-	pkgs, err := packages.Load(cfg, "file="+source)
+	packageImport, err := parsePackageImport(source, srcDir)
 	if err != nil {
 		return nil, err
 	}
-	if packages.PrintErrors(pkgs) > 0 || len(pkgs) == 0 {
-		return nil, errors.New("loading package failed")
-	}
-
-	packageImport := pkgs[0].PkgPath
-
-	// It is illegal to import a _test package.
-	packageImport = strings.TrimSuffix(packageImport, "_test")
 
 	fs := token.NewFileSet()
 	file, err := parser.ParseFile(fs, source, nil, 0)
@@ -453,15 +445,15 @@ func importsOfFile(file *ast.File) (normalImports map[string]string, dotImports
 			}
 			pkgName = is.Name.Name
 		} else {
-			pkg, err := build.Import(importPath, "", 0)
-			if err != nil {
+			pkg, ok := lookupPackageName(importPath)
+			if !ok {
 				// Fallback to import path suffix. Note that this is uncertain.
 				_, last := path.Split(importPath)
 				// If the last path component has dots, the first dot-delimited
 				// field is used as the name.
 				pkgName = strings.SplitN(last, ".", 2)[0]
 			} else {
-				pkgName = pkg.Name
+				pkgName = pkg
 			}
 		}
 
@@ -519,3 +511,46 @@ func isVariadic(f *ast.FuncType) bool {
 	_, ok := f.Params.List[nargs-1].Type.(*ast.Ellipsis)
 	return ok
 }
+
+// packageNameOfDir get package import path via dir
+func packageNameOfDir(srcDir string) (string, error) {
+	files, err := ioutil.ReadDir(srcDir)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	var goFilePath string
+	for _, file := range files {
+		if !file.IsDir() && strings.HasSuffix(file.Name(), ".go") {
+			goFilePath = file.Name()
+			break
+		}
+	}
+	if goFilePath == "" {
+		return "", fmt.Errorf("go source file not found %s", srcDir)
+	}
+
+	packageImport, err := parsePackageImport(goFilePath, srcDir)
+	if err != nil {
+		return "", err
+	}
+	return packageImport, nil
+}
+
+// parseImportPackage get package import path via source file
+func parsePackageImport(source, srcDir string) (string, error) {
+	cfg := &packages.Config{Mode: packages.LoadFiles, Tests: true, Dir: srcDir}
+	pkgs, err := packages.Load(cfg, "file="+source)
+	if err != nil {
+		return "", err
+	}
+	if packages.PrintErrors(pkgs) > 0 || len(pkgs) == 0 {
+		return "", errors.New("loading package failed")
+	}
+
+	packageImport := pkgs[0].PkgPath
+
+	// It is illegal to import a _test package.
+	packageImport = strings.TrimSuffix(packageImport, "_test")
+	return packageImport, nil
+}
diff --git a/mockgen/version.1.11.go b/mockgen/version.1.11.go
new file mode 100644
index 0000000..e47183d
--- /dev/null
+++ b/mockgen/version.1.11.go
@@ -0,0 +1,26 @@
+// Copyright 2019 Google LLC
+//
+// 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.
+
+// +build !go1.12
+
+package main
+
+import (
+	"log"
+)
+
+func printVersion() {
+	log.Printf("No version information is available for Mockgen compiled with " +
+		"version 1.11")
+}
diff --git a/mockgen/version.1.12.go b/mockgen/version.1.12.go
new file mode 100644
index 0000000..3cc2ad8
--- /dev/null
+++ b/mockgen/version.1.12.go
@@ -0,0 +1,35 @@
+// Copyright 2019 Google LLC
+//
+// 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.
+//
+
+// +build go1.12
+
+package main
+
+import (
+	"fmt"
+	"log"
+	"runtime/debug"
+)
+
+func printVersion() {
+	if bi, exists := debug.ReadBuildInfo(); exists {
+		fmt.Println(bi.Main.Version)
+	} else {
+		log.Printf("No version information found. Make sure to use " +
+			"GO111MODULE=on when running 'go get' in order to use specific " +
+			"version of the binary.")
+	}
+
+}
diff --git a/sample/mock_user/mock_user.go b/sample/mock_user/mock_user.go
index 4cf24bb..34fec0d 100644
--- a/sample/mock_user/mock_user.go
+++ b/sample/mock_user/mock_user.go
@@ -11,7 +11,7 @@ import (
 	imp1 "github.com/golang/mock/sample/imp1"
 	imp2 "github.com/golang/mock/sample/imp2"
 	imp3 "github.com/golang/mock/sample/imp3"
-	imp4 "github.com/golang/mock/sample/imp4"
+	imp_four "github.com/golang/mock/sample/imp4"
 	hash "hash"
 	template "html/template"
 	io "io"
@@ -115,7 +115,7 @@ func (mr *MockIndexMockRecorder) EllipOnly(arg0 ...interface{}) *gomock.Call {
 }
 
 // ForeignFour mocks base method
-func (m *MockIndex) ForeignFour(arg0 imp4.Imp4) {
+func (m *MockIndex) ForeignFour(arg0 imp_four.Imp4) {
 	m.ctrl.T.Helper()
 	m.ctrl.Call(m, "ForeignFour", arg0)
 }
diff --git a/tools.go b/tools.go
new file mode 100644
index 0000000..c1ea623
--- /dev/null
+++ b/tools.go
@@ -0,0 +1,22 @@
+// +build tools
+
+// 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 mock
+
+// These imports are included here so they don't disappear from go.mod file.
+import (
+	_ "rsc.io/quote/v3"
+)


Test patch:
diff --git a/mockgen/internal/tests/custom_package_name/greeter/greeter_mock_test.go b/mockgen/internal/tests/custom_package_name/greeter/greeter_mock_test.go
index 0885f3d..41277d5 100644
--- a/mockgen/internal/tests/custom_package_name/greeter/greeter_mock_test.go
+++ b/mockgen/internal/tests/custom_package_name/greeter/greeter_mock_test.go
@@ -6,7 +6,7 @@ package greeter
 
 import (
 	gomock "github.com/golang/mock/gomock"
-	v1 "github.com/golang/mock/mockgen/internal/tests/custom_package_name/client/v1"
+	client "github.com/golang/mock/mockgen/internal/tests/custom_package_name/client/v1"
 	reflect "reflect"
 )
 
@@ -34,10 +34,10 @@ func (m *MockInputMaker) EXPECT() *MockInputMakerMockRecorder {
 }
 
 // MakeInput mocks base method
-func (m *MockInputMaker) MakeInput() v1.GreetInput {
+func (m *MockInputMaker) MakeInput() client.GreetInput {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "MakeInput")
-	ret0, _ := ret[0].(v1.GreetInput)
+	ret0, _ := ret[0].(client.GreetInput)
 	return ret0
 }
 
diff --git a/mockgen/internal/tests/import_source/source_mock.go b/mockgen/internal/tests/import_source/source_mock.go
index d7c4769..847112d 100644
--- a/mockgen/internal/tests/import_source/source_mock.go
+++ b/mockgen/internal/tests/import_source/source_mock.go
@@ -6,7 +6,7 @@ package mock_source
 
 import (
 	gomock "github.com/golang/mock/gomock"
-	definition "github.com/golang/mock/mockgen/internal/tests/import_source/definition"
+	source "github.com/golang/mock/mockgen/internal/tests/import_source/definition"
 	reflect "reflect"
 )
 
@@ -34,7 +34,7 @@ func (m *MockS) EXPECT() *MockSMockRecorder {
 }
 
 // F mocks base method
-func (m *MockS) F(arg0 definition.X) {
+func (m *MockS) F(arg0 source.X) {
 	m.ctrl.T.Helper()
 	m.ctrl.Call(m, "F", arg0)
 }
diff --git a/mockgen/mockgen_test.go b/mockgen/mockgen_test.go
index 1c139d7..130d3cf 100644
--- a/mockgen/mockgen_test.go
+++ b/mockgen/mockgen_test.go
@@ -334,3 +334,31 @@ func TestGetArgNames(t *testing.T) {
 		})
 	}
 }
+
+func Test_lookupPackageName(t *testing.T) {
+	type args struct {
+		importPath string
+	}
+	tests := []struct {
+		name            string
+		importPath      string
+		wantPackageName string
+		wantOK          bool
+	}{
+		{"golang package", "context", "context", true},
+		{"third party", "golang.org/x/tools/present", "present", true},
+		{"modules", "rsc.io/quote/v3", "quote", true},
+		{"fail", "this/should/not/work", "", false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotPackageName, gotOk := lookupPackageName(tt.importPath)
+			if gotPackageName != tt.wantPackageName {
+				t.Errorf("lookupPackageName() gotPackageName = %v, wantPackageName %v", gotPackageName, tt.wantPackageName)
+			}
+			if gotOk != tt.wantOK {
+				t.Errorf("lookupPackageName() gotOk = %v, wantOK %v", gotOk, tt.wantOK)
+			}
+		})
+	}
+}