gover.go raw

   1  // Copyright 2023 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  // This is a fork of internal/gover for use by x/tools until
   6  // go1.21 and earlier are no longer supported by x/tools.
   7  
   8  package versions
   9  
  10  import "strings"
  11  
  12  // A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]]
  13  // The numbers are the original decimal strings to avoid integer overflows
  14  // and since there is very little actual math. (Probably overflow doesn't matter in practice,
  15  // but at the time this code was written, there was an existing test that used
  16  // go1.99999999999, which does not fit in an int on 32-bit platforms.
  17  // The "big decimal" representation avoids the problem entirely.)
  18  type gover struct {
  19  	major string // decimal
  20  	minor string // decimal or ""
  21  	patch string // decimal or ""
  22  	kind  string // "", "alpha", "beta", "rc"
  23  	pre   string // decimal or ""
  24  }
  25  
  26  // compare returns -1, 0, or +1 depending on whether
  27  // x < y, x == y, or x > y, interpreted as toolchain versions.
  28  // The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
  29  // Malformed versions compare less than well-formed versions and equal to each other.
  30  // The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
  31  func compare(x, y string) int {
  32  	vx := parse(x)
  33  	vy := parse(y)
  34  
  35  	if c := cmpInt(vx.major, vy.major); c != 0 {
  36  		return c
  37  	}
  38  	if c := cmpInt(vx.minor, vy.minor); c != 0 {
  39  		return c
  40  	}
  41  	if c := cmpInt(vx.patch, vy.patch); c != 0 {
  42  		return c
  43  	}
  44  	if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc
  45  		return c
  46  	}
  47  	if c := cmpInt(vx.pre, vy.pre); c != 0 {
  48  		return c
  49  	}
  50  	return 0
  51  }
  52  
  53  // lang returns the Go language version. For example, lang("1.2.3") == "1.2".
  54  func lang(x string) string {
  55  	v := parse(x)
  56  	if v.minor == "" || v.major == "1" && v.minor == "0" {
  57  		return v.major
  58  	}
  59  	return v.major + "." + v.minor
  60  }
  61  
  62  // isValid reports whether the version x is valid.
  63  func isValid(x string) bool {
  64  	return parse(x) != gover{}
  65  }
  66  
  67  // parse parses the Go version string x into a version.
  68  // It returns the zero version if x is malformed.
  69  func parse(x string) gover {
  70  	var v gover
  71  
  72  	// Parse major version.
  73  	var ok bool
  74  	v.major, x, ok = cutInt(x)
  75  	if !ok {
  76  		return gover{}
  77  	}
  78  	if x == "" {
  79  		// Interpret "1" as "1.0.0".
  80  		v.minor = "0"
  81  		v.patch = "0"
  82  		return v
  83  	}
  84  
  85  	// Parse . before minor version.
  86  	if x[0] != '.' {
  87  		return gover{}
  88  	}
  89  
  90  	// Parse minor version.
  91  	v.minor, x, ok = cutInt(x[1:])
  92  	if !ok {
  93  		return gover{}
  94  	}
  95  	if x == "" {
  96  		// Patch missing is same as "0" for older versions.
  97  		// Starting in Go 1.21, patch missing is different from explicit .0.
  98  		if cmpInt(v.minor, "21") < 0 {
  99  			v.patch = "0"
 100  		}
 101  		return v
 102  	}
 103  
 104  	// Parse patch if present.
 105  	if x[0] == '.' {
 106  		v.patch, x, ok = cutInt(x[1:])
 107  		if !ok || x != "" {
 108  			// Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
 109  			// Allowing them would be a bit confusing because we already have:
 110  			//	1.21 < 1.21rc1
 111  			// But a prerelease of a patch would have the opposite effect:
 112  			//	1.21.3rc1 < 1.21.3
 113  			// We've never needed them before, so let's not start now.
 114  			return gover{}
 115  		}
 116  		return v
 117  	}
 118  
 119  	// Parse prerelease.
 120  	i := 0
 121  	for i < len(x) && (x[i] < '0' || '9' < x[i]) {
 122  		if x[i] < 'a' || 'z' < x[i] {
 123  			return gover{}
 124  		}
 125  		i++
 126  	}
 127  	if i == 0 {
 128  		return gover{}
 129  	}
 130  	v.kind, x = x[:i], x[i:]
 131  	if x == "" {
 132  		return v
 133  	}
 134  	v.pre, x, ok = cutInt(x)
 135  	if !ok || x != "" {
 136  		return gover{}
 137  	}
 138  
 139  	return v
 140  }
 141  
 142  // cutInt scans the leading decimal number at the start of x to an integer
 143  // and returns that value and the rest of the string.
 144  func cutInt(x string) (n, rest string, ok bool) {
 145  	i := 0
 146  	for i < len(x) && '0' <= x[i] && x[i] <= '9' {
 147  		i++
 148  	}
 149  	if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero
 150  		return "", "", false
 151  	}
 152  	return x[:i], x[i:], true
 153  }
 154  
 155  // cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
 156  // (Copied from golang.org/x/mod/semver's compareInt.)
 157  func cmpInt(x, y string) int {
 158  	if x == y {
 159  		return 0
 160  	}
 161  	if len(x) < len(y) {
 162  		return -1
 163  	}
 164  	if len(x) > len(y) {
 165  		return +1
 166  	}
 167  	if x < y {
 168  		return -1
 169  	} else {
 170  		return +1
 171  	}
 172  }
 173