static (SVersion?Version, int FMajor, int FMinor, string?Error, bool FourtPartLost) TryMatchFloatingVersion(ref ReadOnlySpan <char> s) { // Handling the marvelous "" (empty string), that is like '*'. if (s.Length == 0) { return(null, -1, 0, null, false); } var version = SVersion.TryParse(ref s); if (version.IsValid) { // If only the 3 first parts have been read: launch SkipExtraPartsAndPrereleaseIfAny on the head to skip // potential extra parts and prerelease. return(version, 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } int major, minor = -1; if (TryMatchXStarInt(ref s, out major)) { if (major >= 0) { if (s.Length > 0 && TryMatch(ref s, '.')) { if (s.Length == 0 || !TryMatchXStarInt(ref s, out minor)) { return(null, 0, 0, "Expecting minor number or *.", false); } if (minor >= 0) { // If a fourth part caused the version parse to fail, handle it here. if (s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out int patch)) { return(SVersion.Create(major, minor, patch), 0, 0, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } } } } else { minor = 0; } // Forgetting any trailing "X.Y.*" since it is like "X.Y". // Even if the npm grammar allows "3.*-alpha" or "3.1.*+meta", we ignores this: https://semver.npmjs.com/ selects nothing. // We consider this stupid trail as being FourthPartLost. return(null, major, minor, null, SkipExtraPartsAndPrereleaseIfAny(ref s)); } return(null, 0, 0, version.ErrorMessage, false); }
static (ParseResult Result, bool IsFloatingMinor) TryMatchRangeAlone(ref ReadOnlySpan <char> s, SVersionLock defaultBound, bool includePrerelease) { var r = TryMatchFloatingVersion(ref s); if (r.Error != null) { return(new ParseResult(r.Error), false); } PackageQuality quality = includePrerelease ? PackageQuality.None : PackageQuality.Stable; if (r.Version != null) { // As soon as a prerelease appears, this can only be an approximation (with one exception - see below) since for npm: // // "For example, the range >1.2.3-alpha.3 would be allowed to match the version 1.2.3-alpha.7, but it // would not be satisfied by 3.4.5-alpha.9, even though 3.4.5-alpha.9 is technically "greater than" // 1.2.3-alpha.3 according to the SemVer sort rules. The version range only accepts prerelease tags // on the 1.2.3 version. The version 3.4.5 would satisfy the range, because it does not have a prerelease // flag, and 3.4.5 is greater than 1.2.3-alpha.7." // // We also set the MinQuality to CI (otherwise alpha.7 will not be authorized for alpha.3) regardless of the includePrerelease. // Moreover, if we are coming from the ~ (tilde range), the lock is on the patch, not on the minor, and this exactly matches the // npm behavior. // bool isApproximated = !includePrerelease; if (r.Version.IsPrerelease) { quality = PackageQuality.None; if (defaultBound == SVersionLock.LockMinor) { defaultBound = SVersionLock.LockPatch; isApproximated = false; } } return(new ParseResult(new SVersionBound(r.Version, defaultBound, quality), isApproximated, r.FourtPartLost), false); } if (r.FMajor < 0) { return(new ParseResult(new SVersionBound(_000Version, SVersionLock.None, quality), isApproximated: !includePrerelease, false), false); } if (r.FMinor < 0) { return(new ParseResult(new SVersionBound(SVersion.Create(r.FMajor, 0, 0), SVersionLock.LockMajor, quality), isApproximated: !includePrerelease, false), true); } return(new ParseResult(new SVersionBound(SVersion.Create(r.FMajor, r.FMinor, 0), SVersionLock.LockMinor, quality), isApproximated: !includePrerelease, false), false); }
static SVersion TryParseVersion(ref ReadOnlySpan <char> s, out bool fourthPartLost) { Debug.Assert(s.Length > 0); fourthPartLost = false; var v = SVersion.TryParse(ref s); if (v.IsValid) { // If only the 3 first parts have been read... fourthPartLost = SkipExtraPartsAndPrereleaseIfAny(ref s); } else { if (TryMatchNonNegativeInt(ref s, out int major)) { if (s.Length == 0 || !TryMatch(ref s, '.')) { return(SVersion.Create(major, 0, 0)); } if (!TryMatchNonNegativeInt(ref s, out int minor)) { return(new SVersion("Expected Nuget minor part.", null)); } // Try to save the fourth part: in such case the patch is read. int patch = 0; if (s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out patch) && s.Length > 0 && TryMatch(ref s, '.') && s.Length > 0 && TryMatchNonNegativeInt(ref s, out int _)) { fourthPartLost = true; } return(SVersion.Create(major, minor, patch)); } } return(v); }