From 40ee0cfd165ebbd81ce3915d5d55c530ae89eff3 Mon Sep 17 00:00:00 2001 From: walle250ai Date: Tue, 28 Apr 2026 20:51:59 +0800 Subject: [PATCH] feat: add IsPrerelease and IsStable methods to Version --- version.go | 30 +++++++++++++++ version_test.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/version.go b/version.go index 2627f13..5993625 100644 --- a/version.go +++ b/version.go @@ -366,6 +366,17 @@ func (v Version) Metadata() string { return v.metadata } +// IsPrerelease returns true if the version has a prerelease identifier. +func (v *Version) IsPrerelease() bool { + return v.Prerelease() != "" +} + +// IsStable returns true if the version is a stable release. +// A version is stable when Major >= 1 and there's no prerelease identifier. +func (v *Version) IsStable() bool { + return v.Major() >= 1 && v.Prerelease() == "" +} + // originalVPrefix returns the original 'v' prefix if any. func (v Version) originalVPrefix() string { // Note, only lowercase v is supported as a prefix by the parser. @@ -500,6 +511,25 @@ func (v *Version) Equal(o *Version) bool { return v.Compare(o) == 0 } +// Diff returns the difference type between two versions. +// It returns "major", "minor", "patch", "prerelease", or "" if the versions +// are equal. Metadata differences are ignored. +func (v *Version) Diff(o *Version) string { + if v.Major() != o.Major() { + return "major" + } + if v.Minor() != o.Minor() { + return "minor" + } + if v.Patch() != o.Patch() { + return "patch" + } + if v.Prerelease() != o.Prerelease() { + return "prerelease" + } + return "" +} + // Compare compares this version to another one. It returns -1, 0, or 1 if // the version smaller, equal, or larger than the other version. // diff --git a/version_test.go b/version_test.go index 03980ec..a25dde5 100644 --- a/version_test.go +++ b/version_test.go @@ -1027,6 +1027,105 @@ func TestValidateMetadata(t *testing.T) { } } +func TestDiff(t *testing.T) { + tests := []struct { + v1 string + v2 string + expected string + }{ + {"1.2.3", "2.2.3", "major"}, + {"1.2.3", "1.3.3", "minor"}, + {"1.2.3", "1.2.4", "patch"}, + {"1.2.3", "1.2.3-alpha", "prerelease"}, + {"1.2.3-alpha", "1.2.3-beta", "prerelease"}, + {"1.2.3-alpha.1", "1.2.3-alpha.2", "prerelease"}, + {"1.2.3", "1.2.3", ""}, + {"1.2.3+foo", "1.2.3+bar", ""}, + {"1.2.3-alpha+foo", "1.2.3-alpha+bar", ""}, + } + + for _, tc := range tests { + v1, err := NewVersion(tc.v1) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + v2, err := NewVersion(tc.v2) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + a := v1.Diff(v2) + e := tc.expected + if a != e { + t.Errorf( + "Diff of '%s' and '%s' failed. Expected '%s', got '%s'", + tc.v1, tc.v2, e, a, + ) + } + } +} + +func TestIsPrerelease(t *testing.T) { + tests := []struct { + version string + expected bool + }{ + {"1.2.3", false}, + {"1.2.3-alpha", true}, + {"1.2.3-alpha.1", true}, + {"0.3.7", false}, + {"0.3.7-beta", true}, + {"1.2.3-x.7.z.92", true}, + } + + for _, tc := range tests { + v, err := NewVersion(tc.version) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + a := v.IsPrerelease() + e := tc.expected + if a != e { + t.Errorf( + "IsPrerelease of '%s' failed. Expected '%t', got '%t'", + tc.version, e, a, + ) + } + } +} + +func TestIsStable(t *testing.T) { + tests := []struct { + version string + expected bool + }{ + {"0.1.0", false}, + {"0.9.9", false}, + {"1.0.0", true}, + {"1.0.0-beta", false}, + {"2.3.4", true}, + {"1.2.3-alpha.1", false}, + } + + for _, tc := range tests { + v, err := NewVersion(tc.version) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + a := v.IsStable() + e := tc.expected + if a != e { + t.Errorf( + "IsStable of '%s' failed. Expected '%t', got '%t'", + tc.version, e, a, + ) + } + } +} + func FuzzNewVersion(f *testing.F) { testcases := []string{"v1.2.3", " ", "......", "1", "1.2.3-beta.1", "1.2.3+foo", "2.3.4-alpha.1+bar", "lorem ipsum"}