Skip to content

Commit 14e162f

Browse files
authored
fix(internal/semver): replace comparisons with x/mod/semver APIs (#3165)
1 parent a7515e7 commit 14e162f

File tree

3 files changed

+22
-220
lines changed

3 files changed

+22
-220
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/walle/targz v0.0.0-20140417120357-57fe4206da5a
2323
github.com/yuin/goldmark v1.7.13
2424
golang.org/x/exp v0.0.0-20250911091902-df9299821621
25+
golang.org/x/mod v0.30.0
2526
google.golang.org/genproto v0.0.0-20251103181224-f26f9409b101
2627
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda
2728
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda
@@ -261,7 +262,6 @@ require (
261262
go.uber.org/zap v1.27.0 // indirect
262263
golang.org/x/crypto v0.45.0 // indirect
263264
golang.org/x/exp/typeparams v0.0.0-20251002181428-27f1f14c8bb9 // indirect
264-
golang.org/x/mod v0.30.0 // indirect
265265
golang.org/x/net v0.47.0 // indirect
266266
golang.org/x/oauth2 v0.31.0 // indirect
267267
golang.org/x/sync v0.18.0 // indirect

internal/semver/semver.go

Lines changed: 21 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ package semver
1818

1919
import (
2020
"fmt"
21-
"log/slog"
2221
"regexp"
2322
"strconv"
2423
"strings"
24+
25+
"golang.org/x/mod/semver"
2526
)
2627

2728
// Version represents a semantic version.
@@ -109,61 +110,6 @@ func Parse(versionString string) (*Version, error) {
109110
return v, nil
110111
}
111112

112-
// Compare returns an integer comparing two versions.
113-
// The result is -1, 0, or 1 depending on whether v is less than, equal to, or greater than other.
114-
func (v *Version) Compare(other *Version) int {
115-
if v.Major < other.Major {
116-
return -1
117-
}
118-
if v.Major > other.Major {
119-
return 1
120-
}
121-
if v.Minor < other.Minor {
122-
return -1
123-
}
124-
if v.Minor > other.Minor {
125-
return 1
126-
}
127-
if v.Patch < other.Patch {
128-
return -1
129-
}
130-
if v.Patch > other.Patch {
131-
return 1
132-
}
133-
// a pre-release version is less than a non-pre-release version
134-
if v.Prerelease != "" && other.Prerelease == "" {
135-
return -1
136-
}
137-
if v.Prerelease == "" && other.Prerelease != "" {
138-
return 1
139-
}
140-
// lexical comparison between prerelease type (e.g. "alpha" vs "beta")
141-
if v.Prerelease < other.Prerelease {
142-
return -1
143-
}
144-
if v.Prerelease > other.Prerelease {
145-
return 1
146-
}
147-
// prerelease number (e.g. "alpha1" vs "alpha2")
148-
// Note: Lack of prerelease number is considered lower precedence than
149-
// the same prerelease when it has a number - https://semver.org/#spec-item-11.
150-
if v.PrereleaseNumber == nil && other.PrereleaseNumber != nil {
151-
return -1
152-
}
153-
if v.PrereleaseNumber != nil && other.PrereleaseNumber == nil {
154-
return 1
155-
}
156-
if v.PrereleaseNumber != nil && other.PrereleaseNumber != nil {
157-
if *v.PrereleaseNumber < *other.PrereleaseNumber {
158-
return -1
159-
}
160-
if *v.PrereleaseNumber > *other.PrereleaseNumber {
161-
return 1
162-
}
163-
}
164-
return 0
165-
}
166-
167113
// String formats a Version struct into a string.
168114
func (v *Version) String() string {
169115
version := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
@@ -219,22 +165,30 @@ func MaxVersion(versionStrings ...string) string {
219165
if len(versionStrings) == 0 {
220166
return ""
221167
}
222-
versions := make([]*Version, 0)
168+
versions := make([]string, 0)
223169
for _, versionString := range versionStrings {
224-
v, err := Parse(versionString)
225-
if err != nil {
226-
slog.Warn("invalid version string", "version", v)
170+
// Our client versions must not have a "v" prefix.
171+
if strings.HasPrefix(versionString, "v") {
227172
continue
228173
}
229-
versions = append(versions, v)
230-
}
231-
largest := versions[0]
232-
for i := 1; i < len(versions); i++ {
233-
if largest.Compare(versions[i]) < 0 {
234-
largest = versions[i]
174+
175+
// Prepend "v" internally so that we can use [semver.IsValid] and
176+
// [semver.Sort].
177+
vPrefixedString := "v" + versionString
178+
if !semver.IsValid(vPrefixedString) {
179+
continue
235180
}
181+
versions = append(versions, vPrefixedString)
182+
}
183+
184+
if len(versions) == 0 {
185+
return ""
236186
}
237-
return largest.String()
187+
188+
semver.Sort(versions)
189+
190+
// Trim the "v" we prepended to make use of [semver].
191+
return strings.TrimPrefix(versions[len(versions)-1], "v")
238192
}
239193

240194
// ChangeLevel represents the level of change, corresponding to semantic versioning.

internal/semver/semver_test.go

Lines changed: 0 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -252,158 +252,6 @@ func TestDeriveNext(t *testing.T) {
252252
}
253253
}
254254

255-
func TestCompare(t *testing.T) {
256-
for _, test := range []struct {
257-
name string
258-
versionA string
259-
versionB string
260-
want int
261-
}{
262-
{
263-
name: "equal",
264-
versionA: "1.2.3",
265-
versionB: "1.2.3",
266-
want: 0,
267-
},
268-
{
269-
name: "equal with pre-release",
270-
versionA: "1.2.3-alpha",
271-
versionB: "1.2.3-alpha",
272-
want: 0,
273-
},
274-
{
275-
name: "equal with pre-release and number",
276-
versionA: "1.2.3-alpha4",
277-
versionB: "1.2.3-alpha4",
278-
want: 0,
279-
},
280-
{
281-
name: "equal with pre-release and number, different separator",
282-
versionA: "1.2.3-alpha4",
283-
versionB: "1.2.3-alpha.4",
284-
want: 0,
285-
},
286-
{
287-
name: "less than patch",
288-
versionA: "1.2.3",
289-
versionB: "1.2.4",
290-
want: -1,
291-
},
292-
{
293-
name: "less than minor",
294-
versionA: "1.2.3",
295-
versionB: "1.3.0",
296-
want: -1,
297-
},
298-
{
299-
name: "less than major",
300-
versionA: "1.2.3",
301-
versionB: "2.0.0",
302-
want: -1,
303-
},
304-
{
305-
name: "less than prerelease",
306-
versionA: "1.2.3-alpha",
307-
versionB: "1.2.3-beta",
308-
want: -1,
309-
},
310-
{
311-
name: "less than prerelease number",
312-
versionA: "1.2.3-alpha1",
313-
versionB: "1.2.3-alpha2",
314-
want: -1,
315-
},
316-
{
317-
name: "less than prerelease number with separator",
318-
versionA: "1.2.3-alpha.1",
319-
versionB: "1.2.3-alpha.2",
320-
want: -1,
321-
},
322-
{
323-
name: "less than prerelease against stable",
324-
versionA: "1.2.3-alpha1",
325-
versionB: "1.2.3",
326-
want: -1,
327-
},
328-
{
329-
name: "less than prerelease without number",
330-
versionA: "1.2.3-alpha",
331-
versionB: "1.2.3-alpha1",
332-
want: -1,
333-
},
334-
{
335-
name: "greater than patch",
336-
versionA: "1.2.4",
337-
versionB: "1.2.3",
338-
want: 1,
339-
},
340-
{
341-
name: "greater than minor",
342-
versionA: "1.3.0",
343-
versionB: "1.2.3",
344-
want: 1,
345-
},
346-
{
347-
name: "greater than major",
348-
versionA: "2.0.0",
349-
versionB: "1.2.3",
350-
want: 1,
351-
},
352-
{
353-
name: "greater than prerelease",
354-
versionA: "1.2.3-beta",
355-
versionB: "1.2.3-alpha",
356-
want: 1,
357-
},
358-
{
359-
name: "greater than prerelease number",
360-
versionA: "1.2.3-alpha2",
361-
versionB: "1.2.3-alpha1",
362-
want: 1,
363-
},
364-
{
365-
name: "greater than prerelease number with separator",
366-
versionA: "1.2.3-alpha.2",
367-
versionB: "1.2.3-alpha.1",
368-
want: 1,
369-
},
370-
{
371-
name: "greater than prerelease against stable",
372-
versionA: "1.2.3",
373-
versionB: "1.2.3-alpha1",
374-
want: 1,
375-
},
376-
{
377-
name: "greater than prerelease without number",
378-
versionA: "1.2.3-alpha1",
379-
versionB: "1.2.3-alpha",
380-
want: 1,
381-
},
382-
{
383-
// Note: This is SemVer compliant, but may seem odd in practice.
384-
name: "greater prerelease version core than non-prerelease version core",
385-
versionA: "1.2.3-alpha1",
386-
versionB: "1.2.0",
387-
want: 1,
388-
},
389-
} {
390-
t.Run(test.name, func(t *testing.T) {
391-
a, err := Parse(test.versionA)
392-
if err != nil {
393-
t.Fatalf("Parse() returned an error: %v", err)
394-
}
395-
b, err := Parse(test.versionB)
396-
if err != nil {
397-
t.Fatalf("Parse() returned an error: %v", err)
398-
}
399-
got := a.Compare(b)
400-
if diff := cmp.Diff(test.want, got); diff != "" {
401-
t.Errorf("mismatch (-want +got):\n%s", diff)
402-
}
403-
})
404-
}
405-
}
406-
407255
func TestMaxVersion(t *testing.T) {
408256
for _, test := range []struct {
409257
name string

0 commit comments

Comments
 (0)