Source file src/cmd/vendor/golang.org/x/tools/internal/versions/gover.go

     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  

View as plain text