internal/imports: bump relevance for modules with higher major versions

This changes bumps relevance for modules with major versions >1 so the
results for unimported members are ordered by major version.

Updates golang/go#41800

Change-Id: I64f4a1cf78a101acf4229433d06b5793246962f5
Reviewed-on: https://go-review.googlesource.com/c/tools/+/261084
Run-TryBot: Danish Dua <danishdua@google.com>
Trust: Danish Dua <danishdua@google.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/imports/fix.go b/internal/imports/fix.go
index 675d16c..0bd91a8 100644
--- a/internal/imports/fix.go
+++ b/internal/imports/fix.go
@@ -83,7 +83,7 @@
 	IdentName string
 	// FixType is the type of fix this is (AddImport, DeleteImport, SetImportName).
 	FixType   ImportFixType
-	Relevance int // see pkg
+	Relevance float64 // see pkg
 }
 
 // An ImportInfo represents a single import statement.
@@ -592,9 +592,9 @@
 	return fixes, nil
 }
 
-// Highest relevance, used for the standard library. Chosen arbitrarily to
-// match pre-existing gopls code.
-const MaxRelevance = 7
+// MaxRelevance is the highest relevance, used for the standard library.
+// Chosen arbitrarily to match pre-existing gopls code.
+const MaxRelevance = 7.0
 
 // getCandidatePkgs works with the passed callback to find all acceptable packages.
 // It deduplicates by import path, and uses a cached stdlib rather than reading
@@ -658,8 +658,8 @@
 	return resolver.scan(ctx, scanFilter)
 }
 
-func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]int, error) {
-	result := make(map[string]int)
+func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]float64, error) {
+	result := make(map[string]float64)
 	resolver, err := env.GetResolver()
 	if err != nil {
 		return nil, err
@@ -995,7 +995,7 @@
 	// loadExports may be called concurrently.
 	loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error)
 	// scoreImportPath returns the relevance for an import path.
-	scoreImportPath(ctx context.Context, path string) int
+	scoreImportPath(ctx context.Context, path string) float64
 
 	ClearForNewScan()
 }
@@ -1260,10 +1260,10 @@
 }
 
 type pkg struct {
-	dir             string // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
-	importPathShort string // vendorless import path ("net/http", "a/b")
-	packageName     string // package name loaded from source if requested
-	relevance       int    // a weakly-defined score of how relevant a package is. 0 is most relevant.
+	dir             string  // absolute file path to pkg directory ("/usr/lib/go/src/net/http")
+	importPathShort string  // vendorless import path ("net/http", "a/b")
+	packageName     string  // package name loaded from source if requested
+	relevance       float64 // a weakly-defined score of how relevant a package is. 0 is most relevant.
 }
 
 type pkgDistance struct {
@@ -1389,7 +1389,7 @@
 	return nil
 }
 
-func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) int {
+func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) float64 {
 	if _, ok := stdlib[path]; ok {
 		return MaxRelevance
 	}
diff --git a/internal/imports/fix_test.go b/internal/imports/fix_test.go
index 0bcbb88..005bf96 100644
--- a/internal/imports/fix_test.go
+++ b/internal/imports/fix_test.go
@@ -2564,7 +2564,7 @@
 // with correct priorities.
 func TestGetCandidates(t *testing.T) {
 	type res struct {
-		relevance  int
+		relevance  float64
 		name, path string
 	}
 	want := []res{
@@ -2620,7 +2620,7 @@
 
 func TestGetImportPaths(t *testing.T) {
 	type res struct {
-		relevance  int
+		relevance  float64
 		name, path string
 	}
 	want := []res{
@@ -2670,7 +2670,7 @@
 
 func TestGetPackageCompletions(t *testing.T) {
 	type res struct {
-		relevance          int
+		relevance          float64
 		name, path, symbol string
 	}
 	want := []res{
diff --git a/internal/imports/mod.go b/internal/imports/mod.go
index 94880d6..6982121 100644
--- a/internal/imports/mod.go
+++ b/internal/imports/mod.go
@@ -487,7 +487,7 @@
 	return nil
 }
 
-func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int {
+func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) float64 {
 	if _, ok := stdlib[path]; ok {
 		return MaxRelevance
 	}
@@ -495,17 +495,31 @@
 	return modRelevance(mod)
 }
 
-func modRelevance(mod *gocommand.ModuleJSON) int {
+func modRelevance(mod *gocommand.ModuleJSON) float64 {
+	var relevance float64
 	switch {
 	case mod == nil: // out of scope
 		return MaxRelevance - 4
 	case mod.Indirect:
-		return MaxRelevance - 3
+		relevance = MaxRelevance - 3
 	case !mod.Main:
-		return MaxRelevance - 2
+		relevance = MaxRelevance - 2
 	default:
-		return MaxRelevance - 1 // main module ties with stdlib
+		relevance = MaxRelevance - 1 // main module ties with stdlib
 	}
+
+	_, versionString, ok := module.SplitPathVersion(mod.Path)
+	if ok {
+		index := strings.Index(versionString, "v")
+		if index == -1 {
+			return relevance
+		}
+		if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil {
+			relevance += versionNumber / 1000
+		}
+	}
+
+	return relevance
 }
 
 // canonicalize gets the result of canonicalizing the packages using the results
diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go
index 254bc28..5d73c88 100644
--- a/internal/imports/mod_test.go
+++ b/internal/imports/mod_test.go
@@ -890,10 +890,14 @@
 module example.com
 
 require rsc.io/quote v1.5.1
+require rsc.io/quote/v3 v3.0.0
 
 -- rpackage/x.go --
 package rpackage
-import _ "rsc.io/quote"
+import (
+	_ "rsc.io/quote"
+	_ "rsc.io/quote/v3"
+)
 `, "")
 	defer mt.cleanup()
 
@@ -902,7 +906,7 @@
 	}
 
 	type res struct {
-		relevance  int
+		relevance  float64
 		name, path string
 	}
 	want := []res{
@@ -911,6 +915,8 @@
 		{7, "http", "net/http"},
 		// Main module
 		{6, "rpackage", "example.com/rpackage"},
+		// Direct module deps with v2+ major version
+		{5.003, "quote", "rsc.io/quote/v3"},
 		// Direct module deps
 		{5, "quote", "rsc.io/quote"},
 		// Indirect deps
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index 7e932eb..8cee897 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -809,7 +809,7 @@
 			name = pkgToConsider
 		}
 
-		score := float64(pkg.Relevance)
+		score := pkg.Relevance
 		if len(pkgDirList)-1 == depth {
 			score *= highScore
 		} else {
@@ -1102,7 +1102,7 @@
 		paths = append(paths, path)
 	}
 
-	var relevances map[string]int
+	var relevances map[string]float64
 	if len(paths) != 0 {
 		if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
 			var err error
@@ -1174,8 +1174,8 @@
 
 // unimportedScore returns a score for an unimported package that is generally
 // lower than other candidates.
-func unimportedScore(relevance int) float64 {
-	return (stdScore + .1*float64(relevance)) / 2
+func unimportedScore(relevance float64) float64 {
+	return (stdScore + .1*relevance) / 2
 }
 
 func (c *completer) packageMembers(pkg *types.Package, score float64, imp *importInfo) []candidate {
@@ -1398,7 +1398,7 @@
 		paths = append(paths, path)
 	}
 
-	var relevances map[string]int
+	var relevances map[string]float64
 	if len(paths) != 0 {
 		if err := c.snapshot.RunProcessEnvFunc(ctx, func(opts *imports.Options) error {
 			var err error