go/packages: add support for file= queries on ad-hoc packages

This will do a go list using a filename directly if listing using
the directory failed.

Fixes golang/go#32587

Change-Id: Id9993968f0ebc18a455132e0f1468356416a66dd
Reviewed-on: https://go-review.googlesource.com/c/tools/+/182465
Run-TryBot: Michael Matloob <matloob@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/go/packages/golist.go b/go/packages/golist.go
index 72c0c5d..a931fca 100644
--- a/go/packages/golist.go
+++ b/go/packages/golist.go
@@ -231,6 +231,13 @@
 		if err != nil {
 			return err
 		}
+		if len(dirResponse.Roots) == 0 {
+			// Couldn't find a package for the directory. Try to load the file as an ad-hoc package.
+			dirResponse, err = driver(cfg, query)
+			if err != nil {
+				return err
+			}
+		}
 		isRoot := make(map[string]bool, len(dirResponse.Roots))
 		for _, root := range dirResponse.Roots {
 			isRoot[root] = true
diff --git a/go/packages/packages_test.go b/go/packages/packages_test.go
index b5eb366..202125a 100644
--- a/go/packages/packages_test.go
+++ b/go/packages/packages_test.go
@@ -1854,6 +1854,48 @@
 	}
 }
 
+func TestAdHocContains(t *testing.T) { packagestest.TestAll(t, testAdHocContains) }
+func testAdHocContains(t *testing.T, exporter packagestest.Exporter) {
+	exported := packagestest.Export(t, exporter, []packagestest.Module{{
+		Name: "golang.org/fake",
+		Files: map[string]interface{}{
+			"a/a.go": `package a;`,
+		}}})
+	defer exported.Cleanup()
+
+	tmpfile, err := ioutil.TempFile("", "adhoc*.go")
+	filename := tmpfile.Name()
+	if err != nil {
+		t.Fatal(err)
+	}
+	fmt.Fprint(tmpfile, `package main; import "fmt"; func main() { fmt.Println("time for coffee") }`)
+	if err := tmpfile.Close(); err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err := os.Remove(filename); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	exported.Config.Mode = packages.NeedImports | packages.NeedFiles
+	pkgs, err := packages.Load(exported.Config, filename)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(pkgs) != 1 && pkgs[0].PkgPath != "command-line-arguments" {
+		t.Fatalf("packages.Load: want [command-line-arguments], got %v", pkgs)
+	}
+	pkg := pkgs[0]
+	if _, ok := pkg.Imports["fmt"]; !ok || len(pkg.Imports) != 1 {
+		t.Fatalf("Imports of loaded package: want [fmt], got %v", pkg.Imports)
+	}
+	if len(pkg.GoFiles) != 1 || pkg.GoFiles[0] != filename {
+		t.Fatalf("GoFiles of loaded packge: want [%s], got %v", filename, pkg.GoFiles)
+	}
+}
+
 func errorMessages(errors []packages.Error) []string {
 	var msgs []string
 	for _, err := range errors {