semver.go raw

   1  // Copyright 2018 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  // Package semver implements comparison of semantic version strings.
   6  // In this package, semantic version strings must begin with a leading "v",
   7  // as in "v1.0.0".
   8  //
   9  // The general form of a semantic version string accepted by this package is
  10  //
  11  //	vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
  12  //
  13  // where square brackets indicate optional parts of the syntax;
  14  // MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
  15  // PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
  16  // using only alphanumeric characters and hyphens; and
  17  // all-numeric PRERELEASE identifiers must not have leading zeros.
  18  //
  19  // This package follows Semantic Versioning 2.0.0 (see semver.org)
  20  // with two exceptions. First, it requires the "v" prefix. Second, it recognizes
  21  // vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
  22  // as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
  23  package semver
  24  
  25  import (
  26  	"slices"
  27  	"strings"
  28  )
  29  
  30  // parsed returns the parsed form of a semantic version string.
  31  type parsed struct {
  32  	major      string
  33  	minor      string
  34  	patch      string
  35  	short      string
  36  	prerelease string
  37  	build      string
  38  }
  39  
  40  // IsValid reports whether v is a valid semantic version string.
  41  func IsValid(v string) bool {
  42  	_, ok := parse(v)
  43  	return ok
  44  }
  45  
  46  // Canonical returns the canonical formatting of the semantic version v.
  47  // It fills in any missing .MINOR or .PATCH and discards build metadata.
  48  // Two semantic versions compare equal only if their canonical formatting
  49  // is an identical string.
  50  // The canonical invalid semantic version is the empty string.
  51  func Canonical(v string) string {
  52  	p, ok := parse(v)
  53  	if !ok {
  54  		return ""
  55  	}
  56  	if p.build != "" {
  57  		return v[:len(v)-len(p.build)]
  58  	}
  59  	if p.short != "" {
  60  		return v + p.short
  61  	}
  62  	return v
  63  }
  64  
  65  // Major returns the major version prefix of the semantic version v.
  66  // For example, Major("v2.1.0") == "v2".
  67  // If v is an invalid semantic version string, Major returns the empty string.
  68  func Major(v string) string {
  69  	pv, ok := parse(v)
  70  	if !ok {
  71  		return ""
  72  	}
  73  	return v[:1+len(pv.major)]
  74  }
  75  
  76  // MajorMinor returns the major.minor version prefix of the semantic version v.
  77  // For example, MajorMinor("v2.1.0") == "v2.1".
  78  // If v is an invalid semantic version string, MajorMinor returns the empty string.
  79  func MajorMinor(v string) string {
  80  	pv, ok := parse(v)
  81  	if !ok {
  82  		return ""
  83  	}
  84  	i := 1 + len(pv.major)
  85  	if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
  86  		return v[:j]
  87  	}
  88  	return v[:i] + "." + pv.minor
  89  }
  90  
  91  // Prerelease returns the prerelease suffix of the semantic version v.
  92  // For example, Prerelease("v2.1.0-pre+meta") == "-pre".
  93  // If v is an invalid semantic version string, Prerelease returns the empty string.
  94  func Prerelease(v string) string {
  95  	pv, ok := parse(v)
  96  	if !ok {
  97  		return ""
  98  	}
  99  	return pv.prerelease
 100  }
 101  
 102  // Build returns the build suffix of the semantic version v.
 103  // For example, Build("v2.1.0+meta") == "+meta".
 104  // If v is an invalid semantic version string, Build returns the empty string.
 105  func Build(v string) string {
 106  	pv, ok := parse(v)
 107  	if !ok {
 108  		return ""
 109  	}
 110  	return pv.build
 111  }
 112  
 113  // Compare returns an integer comparing two versions according to
 114  // semantic version precedence.
 115  // The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
 116  //
 117  // An invalid semantic version string is considered less than a valid one.
 118  // All invalid semantic version strings compare equal to each other.
 119  func Compare(v, w string) int {
 120  	pv, ok1 := parse(v)
 121  	pw, ok2 := parse(w)
 122  	if !ok1 && !ok2 {
 123  		return 0
 124  	}
 125  	if !ok1 {
 126  		return -1
 127  	}
 128  	if !ok2 {
 129  		return +1
 130  	}
 131  	if c := compareInt(pv.major, pw.major); c != 0 {
 132  		return c
 133  	}
 134  	if c := compareInt(pv.minor, pw.minor); c != 0 {
 135  		return c
 136  	}
 137  	if c := compareInt(pv.patch, pw.patch); c != 0 {
 138  		return c
 139  	}
 140  	return comparePrerelease(pv.prerelease, pw.prerelease)
 141  }
 142  
 143  // Max canonicalizes its arguments and then returns the version string
 144  // that compares greater.
 145  //
 146  // Deprecated: use [Compare] instead. In most cases, returning a canonicalized
 147  // version is not expected or desired.
 148  func Max(v, w string) string {
 149  	v = Canonical(v)
 150  	w = Canonical(w)
 151  	if Compare(v, w) > 0 {
 152  		return v
 153  	}
 154  	return w
 155  }
 156  
 157  // ByVersion implements [sort.Interface] for sorting semantic version strings.
 158  type ByVersion []string
 159  
 160  func (vs ByVersion) Len() int           { return len(vs) }
 161  func (vs ByVersion) Swap(i, j int)      { vs[i], vs[j] = vs[j], vs[i] }
 162  func (vs ByVersion) Less(i, j int) bool { return compareVersion(vs[i], vs[j]) < 0 }
 163  
 164  // Sort sorts a list of semantic version strings using [Compare] and falls back
 165  // to use [strings.Compare] if both versions are considered equal.
 166  func Sort(list []string) {
 167  	slices.SortFunc(list, compareVersion)
 168  }
 169  
 170  func compareVersion(a, b string) int {
 171  	cmp := Compare(a, b)
 172  	if cmp != 0 {
 173  		return cmp
 174  	}
 175  	return strings.Compare(a, b)
 176  }
 177  
 178  func parse(v string) (p parsed, ok bool) {
 179  	if v == "" || v[0] != 'v' {
 180  		return
 181  	}
 182  	p.major, v, ok = parseInt(v[1:])
 183  	if !ok {
 184  		return
 185  	}
 186  	if v == "" {
 187  		p.minor = "0"
 188  		p.patch = "0"
 189  		p.short = ".0.0"
 190  		return
 191  	}
 192  	if v[0] != '.' {
 193  		ok = false
 194  		return
 195  	}
 196  	p.minor, v, ok = parseInt(v[1:])
 197  	if !ok {
 198  		return
 199  	}
 200  	if v == "" {
 201  		p.patch = "0"
 202  		p.short = ".0"
 203  		return
 204  	}
 205  	if v[0] != '.' {
 206  		ok = false
 207  		return
 208  	}
 209  	p.patch, v, ok = parseInt(v[1:])
 210  	if !ok {
 211  		return
 212  	}
 213  	if len(v) > 0 && v[0] == '-' {
 214  		p.prerelease, v, ok = parsePrerelease(v)
 215  		if !ok {
 216  			return
 217  		}
 218  	}
 219  	if len(v) > 0 && v[0] == '+' {
 220  		p.build, v, ok = parseBuild(v)
 221  		if !ok {
 222  			return
 223  		}
 224  	}
 225  	if v != "" {
 226  		ok = false
 227  		return
 228  	}
 229  	ok = true
 230  	return
 231  }
 232  
 233  func parseInt(v string) (t, rest string, ok bool) {
 234  	if v == "" {
 235  		return
 236  	}
 237  	if v[0] < '0' || '9' < v[0] {
 238  		return
 239  	}
 240  	i := 1
 241  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
 242  		i++
 243  	}
 244  	if v[0] == '0' && i != 1 {
 245  		return
 246  	}
 247  	return v[:i], v[i:], true
 248  }
 249  
 250  func parsePrerelease(v string) (t, rest string, ok bool) {
 251  	// "A pre-release version MAY be denoted by appending a hyphen and
 252  	// a series of dot separated identifiers immediately following the patch version.
 253  	// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
 254  	// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
 255  	if v == "" || v[0] != '-' {
 256  		return
 257  	}
 258  	i := 1
 259  	start := 1
 260  	for i < len(v) && v[i] != '+' {
 261  		if !isIdentChar(v[i]) && v[i] != '.' {
 262  			return
 263  		}
 264  		if v[i] == '.' {
 265  			if start == i || isBadNum(v[start:i]) {
 266  				return
 267  			}
 268  			start = i + 1
 269  		}
 270  		i++
 271  	}
 272  	if start == i || isBadNum(v[start:i]) {
 273  		return
 274  	}
 275  	return v[:i], v[i:], true
 276  }
 277  
 278  func parseBuild(v string) (t, rest string, ok bool) {
 279  	if v == "" || v[0] != '+' {
 280  		return
 281  	}
 282  	i := 1
 283  	start := 1
 284  	for i < len(v) {
 285  		if !isIdentChar(v[i]) && v[i] != '.' {
 286  			return
 287  		}
 288  		if v[i] == '.' {
 289  			if start == i {
 290  				return
 291  			}
 292  			start = i + 1
 293  		}
 294  		i++
 295  	}
 296  	if start == i {
 297  		return
 298  	}
 299  	return v[:i], v[i:], true
 300  }
 301  
 302  func isIdentChar(c byte) bool {
 303  	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
 304  }
 305  
 306  func isBadNum(v string) bool {
 307  	i := 0
 308  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
 309  		i++
 310  	}
 311  	return i == len(v) && i > 1 && v[0] == '0'
 312  }
 313  
 314  func isNum(v string) bool {
 315  	i := 0
 316  	for i < len(v) && '0' <= v[i] && v[i] <= '9' {
 317  		i++
 318  	}
 319  	return i == len(v)
 320  }
 321  
 322  func compareInt(x, y string) int {
 323  	if x == y {
 324  		return 0
 325  	}
 326  	if len(x) < len(y) {
 327  		return -1
 328  	}
 329  	if len(x) > len(y) {
 330  		return +1
 331  	}
 332  	if x < y {
 333  		return -1
 334  	} else {
 335  		return +1
 336  	}
 337  }
 338  
 339  func comparePrerelease(x, y string) int {
 340  	// "When major, minor, and patch are equal, a pre-release version has
 341  	// lower precedence than a normal version.
 342  	// Example: 1.0.0-alpha < 1.0.0.
 343  	// Precedence for two pre-release versions with the same major, minor,
 344  	// and patch version MUST be determined by comparing each dot separated
 345  	// identifier from left to right until a difference is found as follows:
 346  	// identifiers consisting of only digits are compared numerically and
 347  	// identifiers with letters or hyphens are compared lexically in ASCII
 348  	// sort order. Numeric identifiers always have lower precedence than
 349  	// non-numeric identifiers. A larger set of pre-release fields has a
 350  	// higher precedence than a smaller set, if all of the preceding
 351  	// identifiers are equal.
 352  	// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
 353  	// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
 354  	if x == y {
 355  		return 0
 356  	}
 357  	if x == "" {
 358  		return +1
 359  	}
 360  	if y == "" {
 361  		return -1
 362  	}
 363  	for x != "" && y != "" {
 364  		x = x[1:] // skip - or .
 365  		y = y[1:] // skip - or .
 366  		var dx, dy string
 367  		dx, x = nextIdent(x)
 368  		dy, y = nextIdent(y)
 369  		if dx != dy {
 370  			ix := isNum(dx)
 371  			iy := isNum(dy)
 372  			if ix != iy {
 373  				if ix {
 374  					return -1
 375  				} else {
 376  					return +1
 377  				}
 378  			}
 379  			if ix {
 380  				if len(dx) < len(dy) {
 381  					return -1
 382  				}
 383  				if len(dx) > len(dy) {
 384  					return +1
 385  				}
 386  			}
 387  			if dx < dy {
 388  				return -1
 389  			} else {
 390  				return +1
 391  			}
 392  		}
 393  	}
 394  	if x == "" {
 395  		return -1
 396  	} else {
 397  		return +1
 398  	}
 399  }
 400  
 401  func nextIdent(x string) (dx, rest string) {
 402  	i := 0
 403  	for i < len(x) && x[i] != '.' {
 404  		i++
 405  	}
 406  	return x[:i], x[i:]
 407  }
 408