1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gover
6
7import (
8	"sort"
9	"strings"
10
11	"golang.org/x/mod/module"
12	"golang.org/x/mod/semver"
13)
14
15// IsToolchain reports whether the module path corresponds to the
16// virtual, non-downloadable module tracking go or toolchain directives in the go.mod file.
17//
18// Note that IsToolchain only matches "go" and "toolchain", not the
19// real, downloadable module "golang.org/toolchain" containing toolchain files.
20//
21//	IsToolchain("go") = true
22//	IsToolchain("toolchain") = true
23//	IsToolchain("golang.org/x/tools") = false
24//	IsToolchain("golang.org/toolchain") = false
25func IsToolchain(path string) bool {
26	return path == "go" || path == "toolchain"
27}
28
29// ModCompare returns the result of comparing the versions x and y
30// for the module with the given path.
31// The path is necessary because the "go" and "toolchain" modules
32// use a different version syntax and semantics (gover, this package)
33// than most modules (semver).
34func ModCompare(path string, x, y string) int {
35	if path == "go" {
36		return Compare(x, y)
37	}
38	if path == "toolchain" {
39		return Compare(maybeToolchainVersion(x), maybeToolchainVersion(y))
40	}
41	return semver.Compare(x, y)
42}
43
44// ModSort is like module.Sort but understands the "go" and "toolchain"
45// modules and their version ordering.
46func ModSort(list []module.Version) {
47	sort.Slice(list, func(i, j int) bool {
48		mi := list[i]
49		mj := list[j]
50		if mi.Path != mj.Path {
51			return mi.Path < mj.Path
52		}
53		// To help go.sum formatting, allow version/file.
54		// Compare semver prefix by semver rules,
55		// file by string order.
56		vi := mi.Version
57		vj := mj.Version
58		var fi, fj string
59		if k := strings.Index(vi, "/"); k >= 0 {
60			vi, fi = vi[:k], vi[k:]
61		}
62		if k := strings.Index(vj, "/"); k >= 0 {
63			vj, fj = vj[:k], vj[k:]
64		}
65		if vi != vj {
66			return ModCompare(mi.Path, vi, vj) < 0
67		}
68		return fi < fj
69	})
70}
71
72// ModIsValid reports whether vers is a valid version syntax for the module with the given path.
73func ModIsValid(path, vers string) bool {
74	if IsToolchain(path) {
75		if path == "toolchain" {
76			return IsValid(FromToolchain(vers))
77		}
78		return IsValid(vers)
79	}
80	return semver.IsValid(vers)
81}
82
83// ModIsPrefix reports whether v is a valid version syntax prefix for the module with the given path.
84// The caller is assumed to have checked that ModIsValid(path, vers) is true.
85func ModIsPrefix(path, vers string) bool {
86	if IsToolchain(path) {
87		if path == "toolchain" {
88			return IsLang(FromToolchain(vers))
89		}
90		return IsLang(vers)
91	}
92	// Semver
93	dots := 0
94	for i := 0; i < len(vers); i++ {
95		switch vers[i] {
96		case '-', '+':
97			return false
98		case '.':
99			dots++
100			if dots >= 2 {
101				return false
102			}
103		}
104	}
105	return true
106}
107
108// ModIsPrerelease reports whether v is a prerelease version for the module with the given path.
109// The caller is assumed to have checked that ModIsValid(path, vers) is true.
110func ModIsPrerelease(path, vers string) bool {
111	if IsToolchain(path) {
112		return IsPrerelease(vers)
113	}
114	return semver.Prerelease(vers) != ""
115}
116
117// ModMajorMinor returns the "major.minor" truncation of the version v,
118// for use as a prefix in "@patch" queries.
119func ModMajorMinor(path, vers string) string {
120	if IsToolchain(path) {
121		if path == "toolchain" {
122			return "go" + Lang(FromToolchain(vers))
123		}
124		return Lang(vers)
125	}
126	return semver.MajorMinor(vers)
127}
128