imports: sibling imports must have matching references

When selecting a sibling's import, the unresolved reference must have
been also used otherwise use the normal search to determine the best
possible package to import.

Fixes golang/go#23001

Change-Id: I38a983569991464970ad5921fe7f280dd3e35a2c
Reviewed-on: https://go-review.googlesource.com/82875
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/imports/fix.go b/imports/fix.go
index 8a0d983..8dbbd27 100644
--- a/imports/fix.go
+++ b/imports/fix.go
@@ -78,6 +78,10 @@
 type packageInfo struct {
 	Globals map[string]bool       // symbol => true
 	Imports map[string]importInfo // pkg base name or alias => info
+	// refs are a set of package references currently satisfied by imports.
+	// first key: either base package (e.g. "fmt") or renamed package
+	// second key: referenced package symbol (e.g. "Println")
+	Refs map[string]map[string]bool
 }
 
 // dirPackageInfo exposes the dirPackageInfoFile function so that it can be overridden.
@@ -93,7 +97,13 @@
 		return nil, err
 	}
 
-	info := &packageInfo{Globals: make(map[string]bool), Imports: make(map[string]importInfo)}
+	info := &packageInfo{
+		Globals: make(map[string]bool),
+		Imports: make(map[string]importInfo),
+		Refs:    make(map[string]map[string]bool),
+	}
+
+	visitor := collectReferences(info.Refs)
 	for _, fi := range packageFileInfos {
 		if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") {
 			continue
@@ -132,10 +142,45 @@
 			}
 			info.Imports[name] = impInfo
 		}
+
+		ast.Walk(visitor, root)
 	}
 	return info, nil
 }
 
+// collectReferences returns a visitor that collects all exported package
+// references
+func collectReferences(refs map[string]map[string]bool) visitFn {
+	var visitor visitFn
+	visitor = func(node ast.Node) ast.Visitor {
+		if node == nil {
+			return visitor
+		}
+		switch v := node.(type) {
+		case *ast.SelectorExpr:
+			xident, ok := v.X.(*ast.Ident)
+			if !ok {
+				break
+			}
+			if xident.Obj != nil {
+				// if the parser can resolve it, it's not a package ref
+				break
+			}
+			pkgName := xident.Name
+			r := refs[pkgName]
+			if r == nil {
+				r = make(map[string]bool)
+				refs[pkgName] = r
+			}
+			if ast.IsExported(v.Sel.Name) {
+				r[v.Sel.Name] = true
+			}
+		}
+		return visitor
+	}
+	return visitor
+}
+
 func fixImports(fset *token.FileSet, f *ast.File, filename string) (added []string, err error) {
 	// refs are a set of possible package references currently unsatisfied by imports.
 	// first key: either base package (e.g. "fmt") or renamed package
@@ -249,8 +294,13 @@
 			if packageInfo != nil {
 				sibling := packageInfo.Imports[pkgName]
 				if sibling.Path != "" {
-					results <- result{ipath: sibling.Path, name: sibling.Alias}
-					return
+					refs := packageInfo.Refs[pkgName]
+					for symbol := range symbols {
+						if refs[symbol] {
+							results <- result{ipath: sibling.Path, name: sibling.Alias}
+							return
+						}
+					}
 				}
 			}
 			ipath, rename, err := findImport(pkgName, symbols, filename)
diff --git a/imports/fix_test.go b/imports/fix_test.go
index d39f28f..3098552 100644
--- a/imports/fix_test.go
+++ b/imports/fix_test.go
@@ -1609,15 +1609,19 @@
 	const provide = `package siblingimporttest
 
 import "local/log"
+import "my/bytes"
 
 func LogSomething() {
 	log.Print("Something")
+	bytes.SomeFunc()
 }
 `
 
 	// need is the file being tested that needs the import.
 	const need = `package siblingimporttest
 
+var _ = bytes.Buffer{}
+
 func LogSomethingElse() {
 	log.Print("Something else")
 }
@@ -1626,7 +1630,12 @@
 	// want is the expected result file
 	const want = `package siblingimporttest
 
-import "local/log"
+import (
+	"bytes"
+	"local/log"
+)
+
+var _ = bytes.Buffer{}
 
 func LogSomethingElse() {
 	log.Print("Something else")