Skip to content

Commit 19d1c37

Browse files
colincornabyWowbaggersLiquidLunch
authored andcommitted
Implement lenient version parsing that doesn't require patch version
Added the `usesLenientParsing` parameter to `Version.init(versionString:) throws` and logic changes to enable parsing version strings without patch versions. Added the `usesLenientParsing: Bool` associated type to `VersionError.invalidVersionCoreIdentifiersCount`, so the error message can be specialised for both strict and lenient parsing.
1 parent df0b2ea commit 19d1c37

File tree

2 files changed

+478
-33
lines changed

2 files changed

+478
-33
lines changed

Sources/TSCUtility/Version.swift

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,29 @@ public struct Version {
4848
/// An error that occurs during the creation of a version.
4949
public enum VersionError: Error, CustomStringConvertible {
5050
/// The version string contains non-ASCII characters.
51+
/// - Parameter versionString: The version string.
5152
case nonASCIIVersionString(_ versionString: String)
5253
/// The version core contains an invalid number of Identifiers.
53-
case invalidVersionCoreIdentifiersCount(_ identifiers: [String])
54+
/// - Parameters:
55+
/// - identifiers: The version core identifiers in the version string.
56+
/// - usesLenientParsing: A Boolean value indicating whether or not the lenient parsing mode was enabled when this error occurred.
57+
case invalidVersionCoreIdentifiersCount(_ identifiers: [String], usesLenientParsing: Bool)
5458
/// Some or all of the version core identifiers contain non-numerical characters or are empty.
59+
/// - Parameter identifiers: The version core identifiers in the version string.
5560
case nonNumericalOrEmptyVersionCoreIdentifiers(_ identifiers: [String])
5661
/// Some or all of the pre-release identifiers contain characters other than alpha-numerics and hyphens.
62+
/// - Parameter identifiers: The pre-release identifiers in the version string.
5763
case nonAlphaNumerHyphenalPrereleaseIdentifiers(_ identifiers: [String])
5864
/// Some or all of the build metadata identifiers contain characters other than alpha-numerics and hyphens.
65+
/// - Parameter identifiers: The build metadata identifiers in the version string.
5966
case nonAlphaNumerHyphenalBuildMetadataIdentifiers(_ identifiers: [String])
6067

6168
public var description: String {
6269
switch self {
6370
case let .nonASCIIVersionString(versionString):
6471
return "non-ASCII characters in version string '\(versionString)'"
65-
case let .invalidVersionCoreIdentifiersCount(identifiers):
66-
return "\(identifiers.count < 3 ? "fewer" : "more") than 3 identifiers in version core '\(identifiers.joined(separator: "."))'"
72+
case let .invalidVersionCoreIdentifiersCount(identifiers, usesLenientParsing):
73+
return "\(identifiers.count > 3 ? "more than 3" : "fewer than \(usesLenientParsing ? 2 : 3)") identifiers in version core '\(identifiers.joined(separator: "."))'"
6774
case let .nonNumericalOrEmptyVersionCoreIdentifiers(identifiers):
6875
if !identifiers.allSatisfy( { !$0.isEmpty } ) {
6976
return "empty identifiers in version core '\(identifiers.joined(separator: "."))'"
@@ -85,15 +92,17 @@ public enum VersionError: Error, CustomStringConvertible {
8592
}
8693

8794
extension Version {
88-
// TODO: Rename this function to `init(string: String) throws`, after `init?(string: String)` is removed.
95+
// TODO: Rename this function to `init(string:usesLenientParsing:) throws`, after `init?(string: String)` is removed.
8996
// TODO: Find a better error-checking order.
9097
// Currently, if a version string is "forty-two", this initializer throws an error that says "forty" is only 1 version core identifier, which is not enough.
9198
// But this is misleading the user to consider "forty" as a valid version core identifier.
9299
// We should find a way to check for (or throw) "wrong characters used" errors first, but without overly-complicating the logic.
93100
/// Creates a version from the given string.
94-
/// - Parameter versionString: The string to create the version from.
101+
/// - Parameters:
102+
/// - versionString: The string to create the version from.
103+
/// - usesLenientParsing: A Boolean value indicating whether or not the version string should be parsed leniently. If `true`, then the patch version is assumed to be `0` if it's not provided in the version string; otherwise, the parsing strictly follows the Semantic Versioning 2.0.0 rules. This value defaults to `false`.
95104
/// - Throws: A `VersionError` instance if the `versionString` doesn't follow [SemVer 2.0.0](https://semver.org).
96-
public init(versionString: String) throws {
105+
public init(versionString: String, usesLenientParsing: Bool = false) throws {
97106
// SemVer 2.0.0 allows only ASCII alphanumerical characters and "-" in the version string, except for "." and "+" as delimiters. ("-" is used as a delimiter between the version core and pre-release identifiers, but it's allowed within pre-release and metadata identifiers as well.)
98107
// Alphanumerics check will come later, after each identifier is split out (i.e. after the delimiters are removed).
99108
guard versionString.allSatisfy(\.isASCII) else {
@@ -107,16 +116,16 @@ extension Version {
107116
let versionCore = versionString[..<(prereleaseDelimiterIndex ?? metadataDelimiterIndex ?? versionString.endIndex)]
108117
let versionCoreIdentifiers = versionCore.split(separator: ".", omittingEmptySubsequences: false)
109118

110-
guard versionCoreIdentifiers.count == 3 else {
111-
throw VersionError.invalidVersionCoreIdentifiersCount(versionCoreIdentifiers.map { String($0) })
119+
guard versionCoreIdentifiers.count == 3 || (usesLenientParsing && versionCoreIdentifiers.count == 2) else {
120+
throw VersionError.invalidVersionCoreIdentifiersCount(versionCoreIdentifiers.map { String($0) }, usesLenientParsing: usesLenientParsing)
112121
}
113122

114123
guard
115124
// Major, minor, and patch versions must be ASCII numbers, according to the semantic versioning standard.
116125
// Converting each identifier from a substring to an integer doubles as checking if the identifiers have non-numeric characters.
117126
let major = Int(versionCoreIdentifiers[0]),
118127
let minor = Int(versionCoreIdentifiers[1]),
119-
let patch = Int(versionCoreIdentifiers[2])
128+
let patch = usesLenientParsing && versionCoreIdentifiers.count == 2 ? 0 : Int(versionCoreIdentifiers[2])
120129
else {
121130
throw VersionError.nonNumericalOrEmptyVersionCoreIdentifiers(versionCoreIdentifiers.map { String($0) })
122131
}

0 commit comments

Comments
 (0)