зеркало из https://github.com/golang/tools.git
internal/imports: import packages from x/mod instead of internal copy
This CL deletes ./internal/module and ./internal/semver, which are copies of packages in golang.org/x/mod. The copies were created before x/mod was a separate module. ./internal/imports is updated to use the packages in x/mod. Change-Id: Id434f5f0a240de97d18505cb7c925c2e062f6231 Reviewed-on: https://go-review.googlesource.com/c/tools/+/218897 Run-TryBot: Jay Conrod <jayconrod@google.com> Run-TryBot: Heschi Kreinick <heschi@google.com> Reviewed-by: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Родитель
3868802a52
Коммит
1ace956b0e
|
@ -14,9 +14,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/mod/semver"
|
||||
"golang.org/x/tools/internal/gopathwalk"
|
||||
"golang.org/x/tools/internal/module"
|
||||
"golang.org/x/tools/internal/semver"
|
||||
)
|
||||
|
||||
// ModuleResolver implements resolver for modules using the go command as little
|
||||
|
@ -579,7 +579,7 @@ func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) dir
|
|||
err: fmt.Errorf("invalid module cache path: %v", subdir),
|
||||
}
|
||||
}
|
||||
modPath, err := module.DecodePath(filepath.ToSlash(matches[1]))
|
||||
modPath, err := module.UnescapePath(filepath.ToSlash(matches[1]))
|
||||
if err != nil {
|
||||
if r.env.Debug {
|
||||
r.env.Logf("decoding module cache path %q: %v", subdir, err)
|
||||
|
|
|
@ -18,8 +18,8 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mod/module"
|
||||
"golang.org/x/tools/internal/gopathwalk"
|
||||
"golang.org/x/tools/internal/module"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
@ -739,7 +739,7 @@ func writeProxyModule(base, arPath string) error {
|
|||
i := strings.LastIndex(arName, "_v")
|
||||
ver := strings.TrimSuffix(arName[i+1:], ".txt")
|
||||
modDir := strings.Replace(arName[:i], "_", "/", -1)
|
||||
modPath, err := module.DecodePath(modDir)
|
||||
modPath, err := module.UnescapePath(modDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,540 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package module defines the module.Version type
|
||||
// along with support code.
|
||||
package module
|
||||
|
||||
// IMPORTANT NOTE
|
||||
//
|
||||
// This file essentially defines the set of valid import paths for the go command.
|
||||
// There are many subtle considerations, including Unicode ambiguity,
|
||||
// security, network, and file system representations.
|
||||
//
|
||||
// This file also defines the set of valid module path and version combinations,
|
||||
// another topic with many subtle considerations.
|
||||
//
|
||||
// Changes to the semantics in this file require approval from rsc.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/internal/semver"
|
||||
)
|
||||
|
||||
// A Version is defined by a module path and version pair.
|
||||
type Version struct {
|
||||
Path string
|
||||
|
||||
// Version is usually a semantic version in canonical form.
|
||||
// There are two exceptions to this general rule.
|
||||
// First, the top-level target of a build has no specific version
|
||||
// and uses Version = "".
|
||||
// Second, during MVS calculations the version "none" is used
|
||||
// to represent the decision to take no version of a given module.
|
||||
Version string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Check checks that a given module path, version pair is valid.
|
||||
// In addition to the path being a valid module path
|
||||
// and the version being a valid semantic version,
|
||||
// the two must correspond.
|
||||
// For example, the path "yaml/v2" only corresponds to
|
||||
// semantic versions beginning with "v2.".
|
||||
func Check(path, version string) error {
|
||||
if err := CheckPath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
if !semver.IsValid(version) {
|
||||
return fmt.Errorf("malformed semantic version %v", version)
|
||||
}
|
||||
_, pathMajor, _ := SplitPathVersion(path)
|
||||
if !MatchPathMajor(version, pathMajor) {
|
||||
if pathMajor == "" {
|
||||
pathMajor = "v0 or v1"
|
||||
}
|
||||
if pathMajor[0] == '.' { // .v1
|
||||
pathMajor = pathMajor[1:]
|
||||
}
|
||||
return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// firstPathOK reports whether r can appear in the first element of a module path.
|
||||
// The first element of the path must be an LDH domain name, at least for now.
|
||||
// To avoid case ambiguity, the domain name must be entirely lower case.
|
||||
func firstPathOK(r rune) bool {
|
||||
return r == '-' || r == '.' ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'a' <= r && r <= 'z'
|
||||
}
|
||||
|
||||
// pathOK reports whether r can appear in an import path element.
|
||||
// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
|
||||
// This matches what "go get" has historically recognized in import paths.
|
||||
// TODO(rsc): We would like to allow Unicode letters, but that requires additional
|
||||
// care in the safe encoding (see note below).
|
||||
func pathOK(r rune) bool {
|
||||
if r < utf8.RuneSelf {
|
||||
return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
|
||||
'0' <= r && r <= '9' ||
|
||||
'A' <= r && r <= 'Z' ||
|
||||
'a' <= r && r <= 'z'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// fileNameOK reports whether r can appear in a file name.
|
||||
// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
|
||||
// If we expand the set of allowed characters here, we have to
|
||||
// work harder at detecting potential case-folding and normalization collisions.
|
||||
// See note about "safe encoding" below.
|
||||
func fileNameOK(r rune) bool {
|
||||
if r < utf8.RuneSelf {
|
||||
// Entire set of ASCII punctuation, from which we remove characters:
|
||||
// ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
|
||||
// We disallow some shell special characters: " ' * < > ? ` |
|
||||
// (Note that some of those are disallowed by the Windows file system as well.)
|
||||
// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
|
||||
// We allow spaces (U+0020) in file names.
|
||||
const allowed = "!#$%&()+,-.=@[]^_{}~ "
|
||||
if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(allowed); i++ {
|
||||
if rune(allowed[i]) == r {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// It may be OK to add more ASCII punctuation here, but only carefully.
|
||||
// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
|
||||
return unicode.IsLetter(r)
|
||||
}
|
||||
|
||||
// CheckPath checks that a module path is valid.
|
||||
func CheckPath(path string) error {
|
||||
if err := checkPath(path, false); err != nil {
|
||||
return fmt.Errorf("malformed module path %q: %v", path, err)
|
||||
}
|
||||
i := strings.Index(path, "/")
|
||||
if i < 0 {
|
||||
i = len(path)
|
||||
}
|
||||
if i == 0 {
|
||||
return fmt.Errorf("malformed module path %q: leading slash", path)
|
||||
}
|
||||
if !strings.Contains(path[:i], ".") {
|
||||
return fmt.Errorf("malformed module path %q: missing dot in first path element", path)
|
||||
}
|
||||
if path[0] == '-' {
|
||||
return fmt.Errorf("malformed module path %q: leading dash in first path element", path)
|
||||
}
|
||||
for _, r := range path[:i] {
|
||||
if !firstPathOK(r) {
|
||||
return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r)
|
||||
}
|
||||
}
|
||||
if _, _, ok := SplitPathVersion(path); !ok {
|
||||
return fmt.Errorf("malformed module path %q: invalid version", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckImportPath checks that an import path is valid.
|
||||
func CheckImportPath(path string) error {
|
||||
if err := checkPath(path, false); err != nil {
|
||||
return fmt.Errorf("malformed import path %q: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPath checks that a general path is valid.
|
||||
// It returns an error describing why but not mentioning path.
|
||||
// Because these checks apply to both module paths and import paths,
|
||||
// the caller is expected to add the "malformed ___ path %q: " prefix.
|
||||
// fileName indicates whether the final element of the path is a file name
|
||||
// (as opposed to a directory name).
|
||||
func checkPath(path string, fileName bool) error {
|
||||
if !utf8.ValidString(path) {
|
||||
return fmt.Errorf("invalid UTF-8")
|
||||
}
|
||||
if path == "" {
|
||||
return fmt.Errorf("empty string")
|
||||
}
|
||||
if strings.Contains(path, "..") {
|
||||
return fmt.Errorf("double dot")
|
||||
}
|
||||
if strings.Contains(path, "//") {
|
||||
return fmt.Errorf("double slash")
|
||||
}
|
||||
if path[len(path)-1] == '/' {
|
||||
return fmt.Errorf("trailing slash")
|
||||
}
|
||||
elemStart := 0
|
||||
for i, r := range path {
|
||||
if r == '/' {
|
||||
if err := checkElem(path[elemStart:i], fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
elemStart = i + 1
|
||||
}
|
||||
}
|
||||
if err := checkElem(path[elemStart:], fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkElem checks whether an individual path element is valid.
|
||||
// fileName indicates whether the element is a file name (not a directory name).
|
||||
func checkElem(elem string, fileName bool) error {
|
||||
if elem == "" {
|
||||
return fmt.Errorf("empty path element")
|
||||
}
|
||||
if strings.Count(elem, ".") == len(elem) {
|
||||
return fmt.Errorf("invalid path element %q", elem)
|
||||
}
|
||||
if elem[0] == '.' && !fileName {
|
||||
return fmt.Errorf("leading dot in path element")
|
||||
}
|
||||
if elem[len(elem)-1] == '.' {
|
||||
return fmt.Errorf("trailing dot in path element")
|
||||
}
|
||||
charOK := pathOK
|
||||
if fileName {
|
||||
charOK = fileNameOK
|
||||
}
|
||||
for _, r := range elem {
|
||||
if !charOK(r) {
|
||||
return fmt.Errorf("invalid char %q", r)
|
||||
}
|
||||
}
|
||||
|
||||
// Windows disallows a bunch of path elements, sadly.
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||
short := elem
|
||||
if i := strings.Index(short, "."); i >= 0 {
|
||||
short = short[:i]
|
||||
}
|
||||
for _, bad := range badWindowsNames {
|
||||
if strings.EqualFold(bad, short) {
|
||||
return fmt.Errorf("disallowed path element %q", elem)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckFilePath checks whether a slash-separated file path is valid.
|
||||
func CheckFilePath(path string) error {
|
||||
if err := checkPath(path, true); err != nil {
|
||||
return fmt.Errorf("malformed file path %q: %v", path, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// badWindowsNames are the reserved file path elements on Windows.
|
||||
// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
|
||||
var badWindowsNames = []string{
|
||||
"CON",
|
||||
"PRN",
|
||||
"AUX",
|
||||
"NUL",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"COM5",
|
||||
"COM6",
|
||||
"COM7",
|
||||
"COM8",
|
||||
"COM9",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"LPT4",
|
||||
"LPT5",
|
||||
"LPT6",
|
||||
"LPT7",
|
||||
"LPT8",
|
||||
"LPT9",
|
||||
}
|
||||
|
||||
// SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
|
||||
// and version is either empty or "/vN" for N >= 2.
|
||||
// As a special case, gopkg.in paths are recognized directly;
|
||||
// they require ".vN" instead of "/vN", and for all N, not just N >= 2.
|
||||
func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
|
||||
if strings.HasPrefix(path, "gopkg.in/") {
|
||||
return splitGopkgIn(path)
|
||||
}
|
||||
|
||||
i := len(path)
|
||||
dot := false
|
||||
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
|
||||
if path[i-1] == '.' {
|
||||
dot = true
|
||||
}
|
||||
i--
|
||||
}
|
||||
if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
|
||||
return path, "", true
|
||||
}
|
||||
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||
if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
|
||||
return path, "", false
|
||||
}
|
||||
return prefix, pathMajor, true
|
||||
}
|
||||
|
||||
// splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
|
||||
func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
|
||||
if !strings.HasPrefix(path, "gopkg.in/") {
|
||||
return path, "", false
|
||||
}
|
||||
i := len(path)
|
||||
if strings.HasSuffix(path, "-unstable") {
|
||||
i -= len("-unstable")
|
||||
}
|
||||
for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
|
||||
i--
|
||||
}
|
||||
if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
|
||||
// All gopkg.in paths must end in vN for some N.
|
||||
return path, "", false
|
||||
}
|
||||
prefix, pathMajor = path[:i-2], path[i-2:]
|
||||
if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
|
||||
return path, "", false
|
||||
}
|
||||
return prefix, pathMajor, true
|
||||
}
|
||||
|
||||
// MatchPathMajor reports whether the semantic version v
|
||||
// matches the path major version pathMajor.
|
||||
func MatchPathMajor(v, pathMajor string) bool {
|
||||
if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
|
||||
pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
|
||||
}
|
||||
if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
|
||||
// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
|
||||
// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
|
||||
return true
|
||||
}
|
||||
m := semver.Major(v)
|
||||
if pathMajor == "" {
|
||||
return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible"
|
||||
}
|
||||
return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:]
|
||||
}
|
||||
|
||||
// CanonicalVersion returns the canonical form of the version string v.
|
||||
// It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
|
||||
func CanonicalVersion(v string) string {
|
||||
cv := semver.Canonical(v)
|
||||
if semver.Build(v) == "+incompatible" {
|
||||
cv += "+incompatible"
|
||||
}
|
||||
return cv
|
||||
}
|
||||
|
||||
// Sort sorts the list by Path, breaking ties by comparing Versions.
|
||||
func Sort(list []Version) {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
mi := list[i]
|
||||
mj := list[j]
|
||||
if mi.Path != mj.Path {
|
||||
return mi.Path < mj.Path
|
||||
}
|
||||
// To help go.sum formatting, allow version/file.
|
||||
// Compare semver prefix by semver rules,
|
||||
// file by string order.
|
||||
vi := mi.Version
|
||||
vj := mj.Version
|
||||
var fi, fj string
|
||||
if k := strings.Index(vi, "/"); k >= 0 {
|
||||
vi, fi = vi[:k], vi[k:]
|
||||
}
|
||||
if k := strings.Index(vj, "/"); k >= 0 {
|
||||
vj, fj = vj[:k], vj[k:]
|
||||
}
|
||||
if vi != vj {
|
||||
return semver.Compare(vi, vj) < 0
|
||||
}
|
||||
return fi < fj
|
||||
})
|
||||
}
|
||||
|
||||
// Safe encodings
|
||||
//
|
||||
// Module paths appear as substrings of file system paths
|
||||
// (in the download cache) and of web server URLs in the proxy protocol.
|
||||
// In general we cannot rely on file systems to be case-sensitive,
|
||||
// nor can we rely on web servers, since they read from file systems.
|
||||
// That is, we cannot rely on the file system to keep rsc.io/QUOTE
|
||||
// and rsc.io/quote separate. Windows and macOS don't.
|
||||
// Instead, we must never require two different casings of a file path.
|
||||
// Because we want the download cache to match the proxy protocol,
|
||||
// and because we want the proxy protocol to be possible to serve
|
||||
// from a tree of static files (which might be stored on a case-insensitive
|
||||
// file system), the proxy protocol must never require two different casings
|
||||
// of a URL path either.
|
||||
//
|
||||
// One possibility would be to make the safe encoding be the lowercase
|
||||
// hexadecimal encoding of the actual path bytes. This would avoid ever
|
||||
// needing different casings of a file path, but it would be fairly illegible
|
||||
// to most programmers when those paths appeared in the file system
|
||||
// (including in file paths in compiler errors and stack traces)
|
||||
// in web server logs, and so on. Instead, we want a safe encoding that
|
||||
// leaves most paths unaltered.
|
||||
//
|
||||
// The safe encoding is this:
|
||||
// replace every uppercase letter with an exclamation mark
|
||||
// followed by the letter's lowercase equivalent.
|
||||
//
|
||||
// For example,
|
||||
// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
|
||||
// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
|
||||
// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
|
||||
//
|
||||
// Import paths that avoid upper-case letters are left unchanged.
|
||||
// Note that because import paths are ASCII-only and avoid various
|
||||
// problematic punctuation (like : < and >), the safe encoding is also ASCII-only
|
||||
// and avoids the same problematic punctuation.
|
||||
//
|
||||
// Import paths have never allowed exclamation marks, so there is no
|
||||
// need to define how to encode a literal !.
|
||||
//
|
||||
// Although paths are disallowed from using Unicode (see pathOK above),
|
||||
// the eventual plan is to allow Unicode letters as well, to assume that
|
||||
// file systems and URLs are Unicode-safe (storing UTF-8), and apply
|
||||
// the !-for-uppercase convention. Note however that not all runes that
|
||||
// are different but case-fold equivalent are an upper/lower pair.
|
||||
// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
|
||||
// are considered to case-fold to each other. When we do add Unicode
|
||||
// letters, we must not assume that upper/lower are the only case-equivalent pairs.
|
||||
// Perhaps the Kelvin symbol would be disallowed entirely, for example.
|
||||
// Or perhaps it would encode as "!!k", or perhaps as "(212A)".
|
||||
//
|
||||
// Also, it would be nice to allow Unicode marks as well as letters,
|
||||
// but marks include combining marks, and then we must deal not
|
||||
// only with case folding but also normalization: both U+00E9 ('é')
|
||||
// and U+0065 U+0301 ('e' followed by combining acute accent)
|
||||
// look the same on the page and are treated by some file systems
|
||||
// as the same path. If we do allow Unicode marks in paths, there
|
||||
// must be some kind of normalization to allow only one canonical
|
||||
// encoding of any character used in an import path.
|
||||
|
||||
// EncodePath returns the safe encoding of the given module path.
|
||||
// It fails if the module path is invalid.
|
||||
func EncodePath(path string) (encoding string, err error) {
|
||||
if err := CheckPath(path); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return encodeString(path)
|
||||
}
|
||||
|
||||
// EncodeVersion returns the safe encoding of the given module version.
|
||||
// Versions are allowed to be in non-semver form but must be valid file names
|
||||
// and not contain exclamation marks.
|
||||
func EncodeVersion(v string) (encoding string, err error) {
|
||||
if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
|
||||
return "", fmt.Errorf("disallowed version string %q", v)
|
||||
}
|
||||
return encodeString(v)
|
||||
}
|
||||
|
||||
func encodeString(s string) (encoding string, err error) {
|
||||
haveUpper := false
|
||||
for _, r := range s {
|
||||
if r == '!' || r >= utf8.RuneSelf {
|
||||
// This should be disallowed by CheckPath, but diagnose anyway.
|
||||
// The correctness of the encoding loop below depends on it.
|
||||
return "", fmt.Errorf("internal error: inconsistency in EncodePath")
|
||||
}
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
haveUpper = true
|
||||
}
|
||||
}
|
||||
|
||||
if !haveUpper {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
for _, r := range s {
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
buf = append(buf, '!', byte(r+'a'-'A'))
|
||||
} else {
|
||||
buf = append(buf, byte(r))
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// DecodePath returns the module path of the given safe encoding.
|
||||
// It fails if the encoding is invalid or encodes an invalid path.
|
||||
func DecodePath(encoding string) (path string, err error) {
|
||||
path, ok := decodeString(encoding)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid module path encoding %q", encoding)
|
||||
}
|
||||
if err := CheckPath(path); err != nil {
|
||||
return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// DecodeVersion returns the version string for the given safe encoding.
|
||||
// It fails if the encoding is invalid or encodes an invalid version.
|
||||
// Versions are allowed to be in non-semver form but must be valid file names
|
||||
// and not contain exclamation marks.
|
||||
func DecodeVersion(encoding string) (v string, err error) {
|
||||
v, ok := decodeString(encoding)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid version encoding %q", encoding)
|
||||
}
|
||||
if err := checkElem(v, true); err != nil {
|
||||
return "", fmt.Errorf("disallowed version string %q", v)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func decodeString(encoding string) (string, bool) {
|
||||
var buf []byte
|
||||
|
||||
bang := false
|
||||
for _, r := range encoding {
|
||||
if r >= utf8.RuneSelf {
|
||||
return "", false
|
||||
}
|
||||
if bang {
|
||||
bang = false
|
||||
if r < 'a' || 'z' < r {
|
||||
return "", false
|
||||
}
|
||||
buf = append(buf, byte(r+'A'-'a'))
|
||||
continue
|
||||
}
|
||||
if r == '!' {
|
||||
bang = true
|
||||
continue
|
||||
}
|
||||
if 'A' <= r && r <= 'Z' {
|
||||
return "", false
|
||||
}
|
||||
buf = append(buf, byte(r))
|
||||
}
|
||||
if bang {
|
||||
return "", false
|
||||
}
|
||||
return string(buf), true
|
||||
}
|
|
@ -1,319 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package module
|
||||
|
||||
import "testing"
|
||||
|
||||
var checkTests = []struct {
|
||||
path string
|
||||
version string
|
||||
ok bool
|
||||
}{
|
||||
{"rsc.io/quote", "0.1.0", false},
|
||||
{"rsc io/quote", "v1.0.0", false},
|
||||
|
||||
{"github.com/go-yaml/yaml", "v0.8.0", true},
|
||||
{"github.com/go-yaml/yaml", "v1.0.0", true},
|
||||
{"github.com/go-yaml/yaml", "v2.0.0", false},
|
||||
{"github.com/go-yaml/yaml", "v2.1.5", false},
|
||||
{"github.com/go-yaml/yaml", "v3.0.0", false},
|
||||
|
||||
{"github.com/go-yaml/yaml/v2", "v1.0.0", false},
|
||||
{"github.com/go-yaml/yaml/v2", "v2.0.0", true},
|
||||
{"github.com/go-yaml/yaml/v2", "v2.1.5", true},
|
||||
{"github.com/go-yaml/yaml/v2", "v3.0.0", false},
|
||||
|
||||
{"gopkg.in/yaml.v0", "v0.8.0", true},
|
||||
{"gopkg.in/yaml.v0", "v1.0.0", false},
|
||||
{"gopkg.in/yaml.v0", "v2.0.0", false},
|
||||
{"gopkg.in/yaml.v0", "v2.1.5", false},
|
||||
{"gopkg.in/yaml.v0", "v3.0.0", false},
|
||||
|
||||
{"gopkg.in/yaml.v1", "v0.8.0", false},
|
||||
{"gopkg.in/yaml.v1", "v1.0.0", true},
|
||||
{"gopkg.in/yaml.v1", "v2.0.0", false},
|
||||
{"gopkg.in/yaml.v1", "v2.1.5", false},
|
||||
{"gopkg.in/yaml.v1", "v3.0.0", false},
|
||||
|
||||
// For gopkg.in, .v1 means v1 only (not v0).
|
||||
// But early versions of vgo still generated v0 pseudo-versions for it.
|
||||
// Even though now we'd generate those as v1 pseudo-versions,
|
||||
// we accept the old pseudo-versions to avoid breaking existing go.mod files.
|
||||
// For example gopkg.in/yaml.v2@v2.2.1's go.mod requires check.v1 at a v0 pseudo-version.
|
||||
{"gopkg.in/check.v1", "v0.0.0", false},
|
||||
{"gopkg.in/check.v1", "v0.0.0-20160102150405-abcdef123456", true},
|
||||
|
||||
{"gopkg.in/yaml.v2", "v1.0.0", false},
|
||||
{"gopkg.in/yaml.v2", "v2.0.0", true},
|
||||
{"gopkg.in/yaml.v2", "v2.1.5", true},
|
||||
{"gopkg.in/yaml.v2", "v3.0.0", false},
|
||||
|
||||
{"rsc.io/quote", "v17.0.0", false},
|
||||
{"rsc.io/quote", "v17.0.0+incompatible", true},
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
for _, tt := range checkTests {
|
||||
err := Check(tt.path, tt.version)
|
||||
if tt.ok && err != nil {
|
||||
t.Errorf("Check(%q, %q) = %v, wanted nil error", tt.path, tt.version, err)
|
||||
} else if !tt.ok && err == nil {
|
||||
t.Errorf("Check(%q, %q) succeeded, wanted error", tt.path, tt.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var checkPathTests = []struct {
|
||||
path string
|
||||
ok bool
|
||||
importOK bool
|
||||
fileOK bool
|
||||
}{
|
||||
{"x.y/z", true, true, true},
|
||||
{"x.y", true, true, true},
|
||||
|
||||
{"", false, false, false},
|
||||
{"x.y/\xFFz", false, false, false},
|
||||
{"/x.y/z", false, false, false},
|
||||
{"x./z", false, false, false},
|
||||
{".x/z", false, false, true},
|
||||
{"-x/z", false, true, true},
|
||||
{"x..y/z", false, false, false},
|
||||
{"x.y/z/../../w", false, false, false},
|
||||
{"x.y//z", false, false, false},
|
||||
{"x.y/z//w", false, false, false},
|
||||
{"x.y/z/", false, false, false},
|
||||
|
||||
{"x.y/z/v0", false, true, true},
|
||||
{"x.y/z/v1", false, true, true},
|
||||
{"x.y/z/v2", true, true, true},
|
||||
{"x.y/z/v2.0", false, true, true},
|
||||
{"X.y/z", false, true, true},
|
||||
|
||||
{"!x.y/z", false, false, true},
|
||||
{"_x.y/z", false, true, true},
|
||||
{"x.y!/z", false, false, true},
|
||||
{"x.y\"/z", false, false, false},
|
||||
{"x.y#/z", false, false, true},
|
||||
{"x.y$/z", false, false, true},
|
||||
{"x.y%/z", false, false, true},
|
||||
{"x.y&/z", false, false, true},
|
||||
{"x.y'/z", false, false, false},
|
||||
{"x.y(/z", false, false, true},
|
||||
{"x.y)/z", false, false, true},
|
||||
{"x.y*/z", false, false, false},
|
||||
{"x.y+/z", false, true, true},
|
||||
{"x.y,/z", false, false, true},
|
||||
{"x.y-/z", true, true, true},
|
||||
{"x.y./zt", false, false, false},
|
||||
{"x.y:/z", false, false, false},
|
||||
{"x.y;/z", false, false, false},
|
||||
{"x.y</z", false, false, false},
|
||||
{"x.y=/z", false, false, true},
|
||||
{"x.y>/z", false, false, false},
|
||||
{"x.y?/z", false, false, false},
|
||||
{"x.y@/z", false, false, true},
|
||||
{"x.y[/z", false, false, true},
|
||||
{"x.y\\/z", false, false, false},
|
||||
{"x.y]/z", false, false, true},
|
||||
{"x.y^/z", false, false, true},
|
||||
{"x.y_/z", false, true, true},
|
||||
{"x.y`/z", false, false, false},
|
||||
{"x.y{/z", false, false, true},
|
||||
{"x.y}/z", false, false, true},
|
||||
{"x.y~/z", false, true, true},
|
||||
{"x.y/z!", false, false, true},
|
||||
{"x.y/z\"", false, false, false},
|
||||
{"x.y/z#", false, false, true},
|
||||
{"x.y/z$", false, false, true},
|
||||
{"x.y/z%", false, false, true},
|
||||
{"x.y/z&", false, false, true},
|
||||
{"x.y/z'", false, false, false},
|
||||
{"x.y/z(", false, false, true},
|
||||
{"x.y/z)", false, false, true},
|
||||
{"x.y/z*", false, false, false},
|
||||
{"x.y/z+", true, true, true},
|
||||
{"x.y/z,", false, false, true},
|
||||
{"x.y/z-", true, true, true},
|
||||
{"x.y/z.t", true, true, true},
|
||||
{"x.y/z/t", true, true, true},
|
||||
{"x.y/z:", false, false, false},
|
||||
{"x.y/z;", false, false, false},
|
||||
{"x.y/z<", false, false, false},
|
||||
{"x.y/z=", false, false, true},
|
||||
{"x.y/z>", false, false, false},
|
||||
{"x.y/z?", false, false, false},
|
||||
{"x.y/z@", false, false, true},
|
||||
{"x.y/z[", false, false, true},
|
||||
{"x.y/z\\", false, false, false},
|
||||
{"x.y/z]", false, false, true},
|
||||
{"x.y/z^", false, false, true},
|
||||
{"x.y/z_", true, true, true},
|
||||
{"x.y/z`", false, false, false},
|
||||
{"x.y/z{", false, false, true},
|
||||
{"x.y/z}", false, false, true},
|
||||
{"x.y/z~", true, true, true},
|
||||
{"x.y/x.foo", true, true, true},
|
||||
{"x.y/aux.foo", false, false, false},
|
||||
{"x.y/prn", false, false, false},
|
||||
{"x.y/prn2", true, true, true},
|
||||
{"x.y/com", true, true, true},
|
||||
{"x.y/com1", false, false, false},
|
||||
{"x.y/com1.txt", false, false, false},
|
||||
{"x.y/calm1", true, true, true},
|
||||
{"github.com/!123/logrus", false, false, true},
|
||||
|
||||
// TODO: CL 41822 allowed Unicode letters in old "go get"
|
||||
// without due consideration of the implications, and only on github.com (!).
|
||||
// For now, we disallow non-ASCII characters in module mode,
|
||||
// in both module paths and general import paths,
|
||||
// until we can get the implications right.
|
||||
// When we do, we'll enable them everywhere, not just for GitHub.
|
||||
{"github.com/user/unicode/испытание", false, false, true},
|
||||
|
||||
{"../x", false, false, false},
|
||||
{"./y", false, false, false},
|
||||
{"x:y", false, false, false},
|
||||
{`\temp\foo`, false, false, false},
|
||||
{".gitignore", false, false, true},
|
||||
{".github/ISSUE_TEMPLATE", false, false, true},
|
||||
{"x☺y", false, false, false},
|
||||
}
|
||||
|
||||
func TestCheckPath(t *testing.T) {
|
||||
for _, tt := range checkPathTests {
|
||||
err := CheckPath(tt.path)
|
||||
if tt.ok && err != nil {
|
||||
t.Errorf("CheckPath(%q) = %v, wanted nil error", tt.path, err)
|
||||
} else if !tt.ok && err == nil {
|
||||
t.Errorf("CheckPath(%q) succeeded, wanted error", tt.path)
|
||||
}
|
||||
|
||||
err = CheckImportPath(tt.path)
|
||||
if tt.importOK && err != nil {
|
||||
t.Errorf("CheckImportPath(%q) = %v, wanted nil error", tt.path, err)
|
||||
} else if !tt.importOK && err == nil {
|
||||
t.Errorf("CheckImportPath(%q) succeeded, wanted error", tt.path)
|
||||
}
|
||||
|
||||
err = CheckFilePath(tt.path)
|
||||
if tt.fileOK && err != nil {
|
||||
t.Errorf("CheckFilePath(%q) = %v, wanted nil error", tt.path, err)
|
||||
} else if !tt.fileOK && err == nil {
|
||||
t.Errorf("CheckFilePath(%q) succeeded, wanted error", tt.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var splitPathVersionTests = []struct {
|
||||
pathPrefix string
|
||||
version string
|
||||
}{
|
||||
{"x.y/z", ""},
|
||||
{"x.y/z", "/v2"},
|
||||
{"x.y/z", "/v3"},
|
||||
{"x.y/v", ""},
|
||||
{"gopkg.in/yaml", ".v0"},
|
||||
{"gopkg.in/yaml", ".v1"},
|
||||
{"gopkg.in/yaml", ".v2"},
|
||||
{"gopkg.in/yaml", ".v3"},
|
||||
}
|
||||
|
||||
func TestSplitPathVersion(t *testing.T) {
|
||||
for _, tt := range splitPathVersionTests {
|
||||
pathPrefix, version, ok := SplitPathVersion(tt.pathPrefix + tt.version)
|
||||
if pathPrefix != tt.pathPrefix || version != tt.version || !ok {
|
||||
t.Errorf("SplitPathVersion(%q) = %q, %q, %v, want %q, %q, true", tt.pathPrefix+tt.version, pathPrefix, version, ok, tt.pathPrefix, tt.version)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range checkPathTests {
|
||||
pathPrefix, version, ok := SplitPathVersion(tt.path)
|
||||
if pathPrefix+version != tt.path {
|
||||
t.Errorf("SplitPathVersion(%q) = %q, %q, %v, doesn't add to input", tt.path, pathPrefix, version, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var encodeTests = []struct {
|
||||
path string
|
||||
enc string // empty means same as path
|
||||
}{
|
||||
{path: "ascii.com/abcdefghijklmnopqrstuvwxyz.-+/~_0123456789"},
|
||||
{path: "github.com/GoogleCloudPlatform/omega", enc: "github.com/!google!cloud!platform/omega"},
|
||||
}
|
||||
|
||||
func TestEncodePath(t *testing.T) {
|
||||
// Check invalid paths.
|
||||
for _, tt := range checkPathTests {
|
||||
if !tt.ok {
|
||||
_, err := EncodePath(tt.path)
|
||||
if err == nil {
|
||||
t.Errorf("EncodePath(%q): succeeded, want error (invalid path)", tt.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check encodings.
|
||||
for _, tt := range encodeTests {
|
||||
enc, err := EncodePath(tt.path)
|
||||
if err != nil {
|
||||
t.Errorf("EncodePath(%q): unexpected error: %v", tt.path, err)
|
||||
continue
|
||||
}
|
||||
want := tt.enc
|
||||
if want == "" {
|
||||
want = tt.path
|
||||
}
|
||||
if enc != want {
|
||||
t.Errorf("EncodePath(%q) = %q, want %q", tt.path, enc, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var badDecode = []string{
|
||||
"github.com/GoogleCloudPlatform/omega",
|
||||
"github.com/!google!cloud!platform!/omega",
|
||||
"github.com/!0google!cloud!platform/omega",
|
||||
"github.com/!_google!cloud!platform/omega",
|
||||
"github.com/!!google!cloud!platform/omega",
|
||||
"",
|
||||
}
|
||||
|
||||
func TestDecodePath(t *testing.T) {
|
||||
// Check invalid decodings.
|
||||
for _, bad := range badDecode {
|
||||
_, err := DecodePath(bad)
|
||||
if err == nil {
|
||||
t.Errorf("DecodePath(%q): succeeded, want error (invalid decoding)", bad)
|
||||
}
|
||||
}
|
||||
|
||||
// Check invalid paths (or maybe decodings).
|
||||
for _, tt := range checkPathTests {
|
||||
if !tt.ok {
|
||||
path, err := DecodePath(tt.path)
|
||||
if err == nil {
|
||||
t.Errorf("DecodePath(%q) = %q, want error (invalid path)", tt.path, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check encodings.
|
||||
for _, tt := range encodeTests {
|
||||
enc := tt.enc
|
||||
if enc == "" {
|
||||
enc = tt.path
|
||||
}
|
||||
path, err := DecodePath(enc)
|
||||
if err != nil {
|
||||
t.Errorf("DecodePath(%q): unexpected error: %v", enc, err)
|
||||
continue
|
||||
}
|
||||
if path != tt.path {
|
||||
t.Errorf("DecodePath(%q) = %q, want %q", enc, path, tt.path)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,388 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package semver implements comparison of semantic version strings.
|
||||
// In this package, semantic version strings must begin with a leading "v",
|
||||
// as in "v1.0.0".
|
||||
//
|
||||
// The general form of a semantic version string accepted by this package is
|
||||
//
|
||||
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
|
||||
//
|
||||
// where square brackets indicate optional parts of the syntax;
|
||||
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
|
||||
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
|
||||
// using only alphanumeric characters and hyphens; and
|
||||
// all-numeric PRERELEASE identifiers must not have leading zeros.
|
||||
//
|
||||
// This package follows Semantic Versioning 2.0.0 (see semver.org)
|
||||
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
|
||||
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
|
||||
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
|
||||
package semver
|
||||
|
||||
// parsed returns the parsed form of a semantic version string.
|
||||
type parsed struct {
|
||||
major string
|
||||
minor string
|
||||
patch string
|
||||
short string
|
||||
prerelease string
|
||||
build string
|
||||
err string
|
||||
}
|
||||
|
||||
// IsValid reports whether v is a valid semantic version string.
|
||||
func IsValid(v string) bool {
|
||||
_, ok := parse(v)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Canonical returns the canonical formatting of the semantic version v.
|
||||
// It fills in any missing .MINOR or .PATCH and discards build metadata.
|
||||
// Two semantic versions compare equal only if their canonical formattings
|
||||
// are identical strings.
|
||||
// The canonical invalid semantic version is the empty string.
|
||||
func Canonical(v string) string {
|
||||
p, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
if p.build != "" {
|
||||
return v[:len(v)-len(p.build)]
|
||||
}
|
||||
if p.short != "" {
|
||||
return v + p.short
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Major returns the major version prefix of the semantic version v.
|
||||
// For example, Major("v2.1.0") == "v2".
|
||||
// If v is an invalid semantic version string, Major returns the empty string.
|
||||
func Major(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v[:1+len(pv.major)]
|
||||
}
|
||||
|
||||
// MajorMinor returns the major.minor version prefix of the semantic version v.
|
||||
// For example, MajorMinor("v2.1.0") == "v2.1".
|
||||
// If v is an invalid semantic version string, MajorMinor returns the empty string.
|
||||
func MajorMinor(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
i := 1 + len(pv.major)
|
||||
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
|
||||
return v[:j]
|
||||
}
|
||||
return v[:i] + "." + pv.minor
|
||||
}
|
||||
|
||||
// Prerelease returns the prerelease suffix of the semantic version v.
|
||||
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
|
||||
// If v is an invalid semantic version string, Prerelease returns the empty string.
|
||||
func Prerelease(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.prerelease
|
||||
}
|
||||
|
||||
// Build returns the build suffix of the semantic version v.
|
||||
// For example, Build("v2.1.0+meta") == "+meta".
|
||||
// If v is an invalid semantic version string, Build returns the empty string.
|
||||
func Build(v string) string {
|
||||
pv, ok := parse(v)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return pv.build
|
||||
}
|
||||
|
||||
// Compare returns an integer comparing two versions according to
|
||||
// according to semantic version precedence.
|
||||
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
|
||||
//
|
||||
// An invalid semantic version string is considered less than a valid one.
|
||||
// All invalid semantic version strings compare equal to each other.
|
||||
func Compare(v, w string) int {
|
||||
pv, ok1 := parse(v)
|
||||
pw, ok2 := parse(w)
|
||||
if !ok1 && !ok2 {
|
||||
return 0
|
||||
}
|
||||
if !ok1 {
|
||||
return -1
|
||||
}
|
||||
if !ok2 {
|
||||
return +1
|
||||
}
|
||||
if c := compareInt(pv.major, pw.major); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.minor, pw.minor); c != 0 {
|
||||
return c
|
||||
}
|
||||
if c := compareInt(pv.patch, pw.patch); c != 0 {
|
||||
return c
|
||||
}
|
||||
return comparePrerelease(pv.prerelease, pw.prerelease)
|
||||
}
|
||||
|
||||
// Max canonicalizes its arguments and then returns the version string
|
||||
// that compares greater.
|
||||
func Max(v, w string) string {
|
||||
v = Canonical(v)
|
||||
w = Canonical(w)
|
||||
if Compare(v, w) > 0 {
|
||||
return v
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func parse(v string) (p parsed, ok bool) {
|
||||
if v == "" || v[0] != 'v' {
|
||||
p.err = "missing v prefix"
|
||||
return
|
||||
}
|
||||
p.major, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad major version"
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.minor = "0"
|
||||
p.patch = "0"
|
||||
p.short = ".0.0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
p.err = "bad minor prefix"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.minor, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad minor version"
|
||||
return
|
||||
}
|
||||
if v == "" {
|
||||
p.patch = "0"
|
||||
p.short = ".0"
|
||||
return
|
||||
}
|
||||
if v[0] != '.' {
|
||||
p.err = "bad patch prefix"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
p.patch, v, ok = parseInt(v[1:])
|
||||
if !ok {
|
||||
p.err = "bad patch version"
|
||||
return
|
||||
}
|
||||
if len(v) > 0 && v[0] == '-' {
|
||||
p.prerelease, v, ok = parsePrerelease(v)
|
||||
if !ok {
|
||||
p.err = "bad prerelease"
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(v) > 0 && v[0] == '+' {
|
||||
p.build, v, ok = parseBuild(v)
|
||||
if !ok {
|
||||
p.err = "bad build"
|
||||
return
|
||||
}
|
||||
}
|
||||
if v != "" {
|
||||
p.err = "junk on end"
|
||||
ok = false
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func parseInt(v string) (t, rest string, ok bool) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
if v[0] < '0' || '9' < v[0] {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if v[0] == '0' && i != 1 {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parsePrerelease(v string) (t, rest string, ok bool) {
|
||||
// "A pre-release version MAY be denoted by appending a hyphen and
|
||||
// a series of dot separated identifiers immediately following the patch version.
|
||||
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
|
||||
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
|
||||
if v == "" || v[0] != '-' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) && v[i] != '+' {
|
||||
if !isIdentChar(v[i]) && v[i] != '.' {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i || isBadNum(v[start:i]) {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func parseBuild(v string) (t, rest string, ok bool) {
|
||||
if v == "" || v[0] != '+' {
|
||||
return
|
||||
}
|
||||
i := 1
|
||||
start := 1
|
||||
for i < len(v) {
|
||||
if !isIdentChar(v[i]) {
|
||||
return
|
||||
}
|
||||
if v[i] == '.' {
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
start = i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
if start == i {
|
||||
return
|
||||
}
|
||||
return v[:i], v[i:], true
|
||||
}
|
||||
|
||||
func isIdentChar(c byte) bool {
|
||||
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
|
||||
}
|
||||
|
||||
func isBadNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v) && i > 1 && v[0] == '0'
|
||||
}
|
||||
|
||||
func isNum(v string) bool {
|
||||
i := 0
|
||||
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
return i == len(v)
|
||||
}
|
||||
|
||||
func compareInt(x, y string) int {
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if len(x) < len(y) {
|
||||
return -1
|
||||
}
|
||||
if len(x) > len(y) {
|
||||
return +1
|
||||
}
|
||||
if x < y {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func comparePrerelease(x, y string) int {
|
||||
// "When major, minor, and patch are equal, a pre-release version has
|
||||
// lower precedence than a normal version.
|
||||
// Example: 1.0.0-alpha < 1.0.0.
|
||||
// Precedence for two pre-release versions with the same major, minor,
|
||||
// and patch version MUST be determined by comparing each dot separated
|
||||
// identifier from left to right until a difference is found as follows:
|
||||
// identifiers consisting of only digits are compared numerically and
|
||||
// identifiers with letters or hyphens are compared lexically in ASCII
|
||||
// sort order. Numeric identifiers always have lower precedence than
|
||||
// non-numeric identifiers. A larger set of pre-release fields has a
|
||||
// higher precedence than a smaller set, if all of the preceding
|
||||
// identifiers are equal.
|
||||
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
|
||||
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
|
||||
if x == y {
|
||||
return 0
|
||||
}
|
||||
if x == "" {
|
||||
return +1
|
||||
}
|
||||
if y == "" {
|
||||
return -1
|
||||
}
|
||||
for x != "" && y != "" {
|
||||
x = x[1:] // skip - or .
|
||||
y = y[1:] // skip - or .
|
||||
var dx, dy string
|
||||
dx, x = nextIdent(x)
|
||||
dy, y = nextIdent(y)
|
||||
if dx != dy {
|
||||
ix := isNum(dx)
|
||||
iy := isNum(dy)
|
||||
if ix != iy {
|
||||
if ix {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if ix {
|
||||
if len(dx) < len(dy) {
|
||||
return -1
|
||||
}
|
||||
if len(dx) > len(dy) {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
if dx < dy {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
}
|
||||
if x == "" {
|
||||
return -1
|
||||
} else {
|
||||
return +1
|
||||
}
|
||||
}
|
||||
|
||||
func nextIdent(x string) (dx, rest string) {
|
||||
i := 0
|
||||
for i < len(x) && x[i] != '.' {
|
||||
i++
|
||||
}
|
||||
return x[:i], x[i:]
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package semver
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"bad", ""},
|
||||
{"v1-alpha.beta.gamma", ""},
|
||||
{"v1-pre", ""},
|
||||
{"v1+meta", ""},
|
||||
{"v1-pre+meta", ""},
|
||||
{"v1.2-pre", ""},
|
||||
{"v1.2+meta", ""},
|
||||
{"v1.2-pre+meta", ""},
|
||||
{"v1.0.0-alpha", "v1.0.0-alpha"},
|
||||
{"v1.0.0-alpha.1", "v1.0.0-alpha.1"},
|
||||
{"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"},
|
||||
{"v1.0.0-beta", "v1.0.0-beta"},
|
||||
{"v1.0.0-beta.2", "v1.0.0-beta.2"},
|
||||
{"v1.0.0-beta.11", "v1.0.0-beta.11"},
|
||||
{"v1.0.0-rc.1", "v1.0.0-rc.1"},
|
||||
{"v1", "v1.0.0"},
|
||||
{"v1.0", "v1.0.0"},
|
||||
{"v1.0.0", "v1.0.0"},
|
||||
{"v1.2", "v1.2.0"},
|
||||
{"v1.2.0", "v1.2.0"},
|
||||
{"v1.2.3-456", "v1.2.3-456"},
|
||||
{"v1.2.3-456.789", "v1.2.3-456.789"},
|
||||
{"v1.2.3-456-789", "v1.2.3-456-789"},
|
||||
{"v1.2.3-456a", "v1.2.3-456a"},
|
||||
{"v1.2.3-pre", "v1.2.3-pre"},
|
||||
{"v1.2.3-pre+meta", "v1.2.3-pre"},
|
||||
{"v1.2.3-pre.1", "v1.2.3-pre.1"},
|
||||
{"v1.2.3-zzz", "v1.2.3-zzz"},
|
||||
{"v1.2.3", "v1.2.3"},
|
||||
{"v1.2.3+meta", "v1.2.3"},
|
||||
{"v1.2.3+meta-pre", "v1.2.3"},
|
||||
}
|
||||
|
||||
func TestIsValid(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
ok := IsValid(tt.in)
|
||||
if ok != (tt.out != "") {
|
||||
t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanonical(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
out := Canonical(tt.in)
|
||||
if out != tt.out {
|
||||
t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMajor(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
out := Major(tt.in)
|
||||
want := ""
|
||||
if i := strings.Index(tt.out, "."); i >= 0 {
|
||||
want = tt.out[:i]
|
||||
}
|
||||
if out != want {
|
||||
t.Errorf("Major(%q) = %q, want %q", tt.in, out, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMajorMinor(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
out := MajorMinor(tt.in)
|
||||
var want string
|
||||
if tt.out != "" {
|
||||
want = tt.in
|
||||
if i := strings.Index(want, "+"); i >= 0 {
|
||||
want = want[:i]
|
||||
}
|
||||
if i := strings.Index(want, "-"); i >= 0 {
|
||||
want = want[:i]
|
||||
}
|
||||
switch strings.Count(want, ".") {
|
||||
case 0:
|
||||
want += ".0"
|
||||
case 1:
|
||||
// ok
|
||||
case 2:
|
||||
want = want[:strings.LastIndex(want, ".")]
|
||||
}
|
||||
}
|
||||
if out != want {
|
||||
t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrerelease(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
pre := Prerelease(tt.in)
|
||||
var want string
|
||||
if tt.out != "" {
|
||||
if i := strings.Index(tt.out, "-"); i >= 0 {
|
||||
want = tt.out[i:]
|
||||
}
|
||||
}
|
||||
if pre != want {
|
||||
t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
build := Build(tt.in)
|
||||
var want string
|
||||
if tt.out != "" {
|
||||
if i := strings.Index(tt.in, "+"); i >= 0 {
|
||||
want = tt.in[i:]
|
||||
}
|
||||
}
|
||||
if build != want {
|
||||
t.Errorf("Build(%q) = %q, want %q", tt.in, build, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
for i, ti := range tests {
|
||||
for j, tj := range tests {
|
||||
cmp := Compare(ti.in, tj.in)
|
||||
var want int
|
||||
if ti.out == tj.out {
|
||||
want = 0
|
||||
} else if i < j {
|
||||
want = -1
|
||||
} else {
|
||||
want = +1
|
||||
}
|
||||
if cmp != want {
|
||||
t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMax(t *testing.T) {
|
||||
for i, ti := range tests {
|
||||
for j, tj := range tests {
|
||||
max := Max(ti.in, tj.in)
|
||||
want := Canonical(ti.in)
|
||||
if i < j {
|
||||
want = Canonical(tj.in)
|
||||
}
|
||||
if max != want {
|
||||
t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
v1 = "v1.0.0+metadata-dash"
|
||||
v2 = "v1.0.0+metadata-dash1"
|
||||
)
|
||||
|
||||
func BenchmarkCompare(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
if Compare(v1, v2) != 0 {
|
||||
b.Fatalf("bad compare")
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче