internal: do not default gopkg.in paths to symbol mode

A gopkg.in path will have the structure gopkg.in/<name>.vN.

Do not redirect searches for <name>.vN to symbol mode by default.

For golang/go#44142

Change-Id: I8e666bd9a1dc2d5fed31f6a1b3512eeda771abc6
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/346111
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/internal/domain.go b/internal/domain.go
index 8bdb07c..b3aa868 100644
--- a/internal/domain.go
+++ b/internal/domain.go
@@ -4,6 +4,12 @@
 
 package internal
 
+import (
+	"strings"
+
+	"golang.org/x/mod/semver"
+)
+
 // TopLevelDomains contains all of the top level domains in the discovery
 // database.
 var TopLevelDomains = map[string]bool{
@@ -184,3 +190,18 @@
 	"yandex":      true,
 	"zone":        true,
 }
+
+// IsGoPkgInPathElement reports whether p is an element of a gopkg.in path.
+// It returns true if p has "gopkg.in" as a prefix, or the suffix is a major
+// version, for example "yaml.v2".
+func IsGoPkgInPathElement(p string) bool {
+	if strings.HasPrefix(strings.ToLower(p), "gopkg.in") {
+		return true
+	}
+	parts := strings.Split(p, ".")
+	if len(parts) <= 1 {
+		return false
+	}
+	maj := parts[len(parts)-1]
+	return semver.Major(maj) == maj
+}
diff --git a/internal/frontend/search.go b/internal/frontend/search.go
index 24e538f..172e6d5 100644
--- a/internal/frontend/search.go
+++ b/internal/frontend/search.go
@@ -375,19 +375,22 @@
 	if prefix := searchModePackage + ":"; strings.HasPrefix(q, prefix) {
 		return strings.TrimPrefix(q, prefix), false
 	}
-	if isPackageDotSymbol(q) {
+	if shouldDefaultToSymbolSearch(q) {
 		return q, true
 	}
 	return q, strings.TrimSpace(r.FormValue("m")) == searchModeSymbol
 }
 
-// isPackageDotSymbol reports whether the search query is of the form
+// shouldDefaultToSymbolSearch reports whether the search query is of the form
 // <package>.<symbol>. The <symbol> component should not be a top-level domain
 // that is in our database.
-func isPackageDotSymbol(q string) bool {
+func shouldDefaultToSymbolSearch(q string) bool {
 	if len(strings.Fields(q)) != 1 || !strings.ContainsAny(q, ".") {
 		return false
 	}
+	if internal.IsGoPkgInPathElement(q) {
+		return false
+	}
 	parts := strings.Split(q, ".")
 	return !internal.TopLevelDomains[parts[len(parts)-1]]
 }
diff --git a/internal/frontend/search_test.go b/internal/frontend/search_test.go
index 3958ad4..3e86bf2 100644
--- a/internal/frontend/search_test.go
+++ b/internal/frontend/search_test.go
@@ -397,7 +397,7 @@
 	}
 }
 
-func TestIsPackageDotSymbol(t *testing.T) {
+func TestShouldDefaultToSymbolSearch(t *testing.T) {
 	for _, test := range []struct {
 		q    string
 		want bool
@@ -409,9 +409,11 @@
 		{"sql", false},
 		{"sql.DB", true},
 		{"sql.DB.Begin", true},
+		{"yaml.v2", false},
+		{"gopkg.in", false},
 	} {
 		t.Run(test.q, func(t *testing.T) {
-			got := isPackageDotSymbol(test.q)
+			got := shouldDefaultToSymbolSearch(test.q)
 			if diff := cmp.Diff(test.want, got); diff != "" {
 				t.Errorf("mismatch (-want +got):\n%s", diff)
 			}
diff --git a/tests/search/scripts/default.txt b/tests/search/scripts/default.txt
index 8d76491..097f5d8 100644
--- a/tests/search/scripts/default.txt
+++ b/tests/search/scripts/default.txt
@@ -13,3 +13,7 @@
 gopkg.in search defaults to package mode
 [package] gopkg.in
 gopkg.in/foo.v1
+
+gopkg.in element search defaults to package mode
+[package] foo.v1
+gopkg.in/foo.v1