semver: add ByVersion and Sort for sorting semantic version lists

For golang/go#44969

Change-Id: I148a18b676061cd8ea481c3f5130d0792c0b5233
Reviewed-on: https://go-review.googlesource.com/c/mod/+/304151
Trust: Jay Conrod <jayconrod@google.com>
Run-TryBot: Jay Conrod <jayconrod@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/semver/semver.go b/semver/semver.go
index 4338f35..7be398f 100644
--- a/semver/semver.go
+++ b/semver/semver.go
@@ -22,6 +22,8 @@
 // as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
 package semver
 
+import "sort"
+
 // parsed returns the parsed form of a semantic version string.
 type parsed struct {
 	major      string
@@ -150,6 +152,24 @@
 	return w
 }
 
+// ByVersion implements sort.Interface for sorting semantic version strings.
+type ByVersion []string
+
+func (vs ByVersion) Len() int      { return len(vs) }
+func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
+func (vs ByVersion) Less(i, j int) bool {
+	cmp := Compare(vs[i], vs[j])
+	if cmp != 0 {
+		return cmp < 0
+	}
+	return vs[i] < vs[j]
+}
+
+// Sort sorts a list of semantic version strings using ByVersion.
+func Sort(list []string) {
+	sort.Sort(ByVersion(list))
+}
+
 func parse(v string) (p parsed, ok bool) {
 	if v == "" || v[0] != 'v' {
 		p.err = "missing v prefix"
diff --git a/semver/semver_test.go b/semver/semver_test.go
index 77025a4..937e18e 100644
--- a/semver/semver_test.go
+++ b/semver/semver_test.go
@@ -5,6 +5,8 @@
 package semver
 
 import (
+	"math/rand"
+	"sort"
 	"strings"
 	"testing"
 )
@@ -154,6 +156,18 @@
 	}
 }
 
+func TestSort(t *testing.T) {
+	versions := make([]string, len(tests))
+	for i, test := range tests {
+		versions[i] = test.in
+	}
+	rand.Shuffle(len(versions), func(i, j int) { versions[i], versions[j] = versions[j], versions[i] })
+	Sort(versions)
+	if !sort.IsSorted(ByVersion(versions)) {
+		t.Errorf("list is not sorted:\n%s", strings.Join(versions, "\n"))
+	}
+}
+
 func TestMax(t *testing.T) {
 	for i, ti := range tests {
 		for j, tj := range tests {