From 1ace956b0e17ff85a6f9bdf6973af28a26234113 Mon Sep 17 00:00:00 2001 From: Jay Conrod Date: Mon, 10 Feb 2020 13:54:36 -0500 Subject: [PATCH] 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 Run-TryBot: Heschi Kreinick Reviewed-by: Heschi Kreinick TryBot-Result: Gobot Gobot --- internal/imports/mod.go | 6 +- internal/imports/mod_test.go | 4 +- internal/module/module.go | 540 --------------------------------- internal/module/module_test.go | 319 ------------------- internal/semver/semver.go | 388 ----------------------- internal/semver/semver_test.go | 182 ----------- 6 files changed, 5 insertions(+), 1434 deletions(-) delete mode 100644 internal/module/module.go delete mode 100644 internal/module/module_test.go delete mode 100644 internal/semver/semver.go delete mode 100644 internal/semver/semver_test.go diff --git a/internal/imports/mod.go b/internal/imports/mod.go index 3ae859ed23..1980f59de5 100644 --- a/internal/imports/mod.go +++ b/internal/imports/mod.go @@ -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) diff --git a/internal/imports/mod_test.go b/internal/imports/mod_test.go index 882be4b129..4df9c02124 100644 --- a/internal/imports/mod_test.go +++ b/internal/imports/mod_test.go @@ -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 } diff --git a/internal/module/module.go b/internal/module/module.go deleted file mode 100644 index 9a4edb9dec..0000000000 --- a/internal/module/module.go +++ /dev/null @@ -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 -} diff --git a/internal/module/module_test.go b/internal/module/module_test.go deleted file mode 100644 index b40bd03dfa..0000000000 --- a/internal/module/module_test.go +++ /dev/null @@ -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, 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) - } - } -} diff --git a/internal/semver/semver.go b/internal/semver/semver.go deleted file mode 100644 index 4af7118e55..0000000000 --- a/internal/semver/semver.go +++ /dev/null @@ -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:] -} diff --git a/internal/semver/semver_test.go b/internal/semver/semver_test.go deleted file mode 100644 index 96b64a5807..0000000000 --- a/internal/semver/semver_test.go +++ /dev/null @@ -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") - } - } -}