cmd/go: strip trailing slash from versioned arguments

'go get' accepts arguments of the form path@version, and it passes
them through search.CleanPatterns before querying proxies. With this
change, CleanPatterns preserves text after '@' and will strip trailing
slashes from the patn.

Previously, we did not strip trailing slashes when a version was
present, which caused proxy base URL validation to fail. Module paths
that end with ".go" (for example, github.com/nats-io/nats.go) use
trailing slashes to prevent 'go build' and other commands from
interpreting packages as source file names, so this caused unnecessary
problems for them.

Updates #32483

Change-Id: Id3730c52089e52f1cac446617c20132a3021a808
Reviewed-on: https://go-review.googlesource.com/c/go/+/194600
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 1cae311..3fcd2d4 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -678,6 +678,15 @@
 	if *getD || len(pkgPatterns) == 0 {
 		return
 	}
+	// TODO(golang.org/issue/32483): handle paths ending with ".go" consistently
+	// with 'go build'. When we load packages above, we interpret arguments as
+	// package patterns, not source files. To preserve that interpretation here,
+	// we add a trailing slash to any patterns ending with ".go".
+	for i := range pkgPatterns {
+		if strings.HasSuffix(pkgPatterns[i], ".go") {
+			pkgPatterns[i] += "/"
+		}
+	}
 	work.BuildInit()
 	pkgs := load.PackagesForBuild(pkgPatterns)
 	work.InstallPackages(pkgPatterns, pkgs)
diff --git a/src/cmd/go/internal/search/search.go b/src/cmd/go/internal/search/search.go
index 0e420c9..33ab4ae 100644
--- a/src/cmd/go/internal/search/search.go
+++ b/src/cmd/go/internal/search/search.go
@@ -363,30 +363,40 @@
 
 // CleanPatterns returns the patterns to use for the given
 // command line. It canonicalizes the patterns but does not
-// evaluate any matches.
+// evaluate any matches. It preserves text after '@' for commands
+// that accept versions.
 func CleanPatterns(patterns []string) []string {
 	if len(patterns) == 0 {
 		return []string{"."}
 	}
 	var out []string
 	for _, a := range patterns {
+		var p, v string
+		if i := strings.IndexByte(a, '@'); i < 0 {
+			p = a
+		} else {
+			p = a[:i]
+			v = a[i:]
+		}
+
 		// Arguments are supposed to be import paths, but
 		// as a courtesy to Windows developers, rewrite \ to /
 		// in command-line arguments. Handles .\... and so on.
 		if filepath.Separator == '\\' {
-			a = strings.ReplaceAll(a, `\`, `/`)
+			p = strings.ReplaceAll(p, `\`, `/`)
 		}
 
 		// Put argument in canonical form, but preserve leading ./.
-		if strings.HasPrefix(a, "./") {
-			a = "./" + path.Clean(a)
-			if a == "./." {
-				a = "."
+		if strings.HasPrefix(p, "./") {
+			p = "./" + path.Clean(p)
+			if p == "./." {
+				p = "."
 			}
 		} else {
-			a = path.Clean(a)
+			p = path.Clean(p)
 		}
-		out = append(out, a)
+
+		out = append(out, p+v)
 	}
 	return out
 }
diff --git a/src/cmd/go/testdata/mod/example.com_dotgo.go_v1.0.0.txt b/src/cmd/go/testdata/mod/example.com_dotgo.go_v1.0.0.txt
new file mode 100644
index 0000000..4f7f4d7
--- /dev/null
+++ b/src/cmd/go/testdata/mod/example.com_dotgo.go_v1.0.0.txt
@@ -0,0 +1,16 @@
+This module's path ends with ".go".
+Based on github.com/nats-io/nats.go.
+Used in regression tests for golang.org/issue/32483.
+
+-- .mod --
+module example.com/dotgo.go
+
+go 1.13
+-- .info --
+{"Version":"v1.0.0"}
+-- go.mod --
+module example.com/dotgo.go
+
+go 1.13
+-- dotgo.go --
+package dotgo
diff --git a/src/cmd/go/testdata/script/mod_get_trailing_slash.txt b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt
new file mode 100644
index 0000000..8828738
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_get_trailing_slash.txt
@@ -0,0 +1,32 @@
+# go list should fail to load a package ending with ".go" since that denotes
+# a source file. However, ".go/" should work.
+# TODO(golang.org/issue/32483): perhaps we should treat non-existent paths
+# with .go suffixes as package paths instead.
+! go list example.com/dotgo.go
+go list example.com/dotgo.go/
+stdout ^example.com/dotgo.go$
+
+# go get -d should succeed in either case, with or without a version.
+# Arguments are interpreted as packages or package patterns with versions,
+# not source files.
+go get -d example.com/dotgo.go
+go get -d example.com/dotgo.go/
+go get -d example.com/dotgo.go@v1.0.0
+go get -d example.com/dotgo.go/@v1.0.0
+
+# go get (without -d) should also succeed in either case.
+# TODO(golang.org/issue/32483): we should be consistent with 'go build',
+# 'go list', and other commands. 'go list example.com/dotgo.go' (above) and
+# 'go get example.com/dotgo.go' should both succeed or both fail.
+[short] skip
+go get example.com/dotgo.go
+go get example.com/dotgo.go/
+go get example.com/dotgo.go@v1.0.0
+go get example.com/dotgo.go/@v1.0.0
+
+-- go.mod --
+module m
+
+go 1.13
+
+require example.com/dotgo.go v1.0.0