diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index c4202737330..75f75cc112f 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -335,8 +335,9 @@ IEnumerator IEnumerable.GetEnumerator() public sealed class SemanticVersion : IComparable, IComparable, IEquatable { private const string VersionSansRegEx = @"^(?\d+)(\.(?\d+))?(\.(?\d+))?$"; - private const string LabelRegEx = @"^((?[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?[0-9A-Za-z][0-9A-Za-z\-\.]*))?$"; - private const string LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*$"; + private const string LabelRegEx = @"^(?(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+(?[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"; + private const string LabelUnitRegEx = @"^((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)$"; + private const string BuildUnitRegEx = @"^([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)$"; private const string PreLabelPropertyName = "PSSemVerPreReleaseLabel"; private const string BuildLabelPropertyName = "PSSemVerBuildLabel"; private const string TypeNameForVersionWithLabel = "System.Version#IncludeLabel"; @@ -370,7 +371,7 @@ public SemanticVersion(string version) /// The build metadata for the version. /// /// If don't match 'LabelUnitRegEx'. - /// If don't match 'LabelUnitRegEx'. + /// If don't match 'BuildUnitRegEx'. /// public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, string buildLabel) : this(major, minor, patch) @@ -387,7 +388,7 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, if (!string.IsNullOrEmpty(buildLabel)) { - if (!Regex.IsMatch(buildLabel, LabelUnitRegEx)) + if (!Regex.IsMatch(buildLabel, BuildUnitRegEx)) { throw new FormatException(nameof(buildLabel)); } @@ -410,7 +411,7 @@ public SemanticVersion(int major, int minor, int patch, string preReleaseLabel, public SemanticVersion(int major, int minor, int patch, string label) : this(major, minor, patch) { - // We presume the SymVer : + // We presume the SemVer : // 1) major.minor.patch-label // 2) 'label' starts with letter or digit. if (!string.IsNullOrEmpty(label)) @@ -447,7 +448,7 @@ public SemanticVersion(int major, int minor, int patch) throw PSTraceSource.NewArgumentException(nameof(minor)); } - if (patch < 0) + if (patch < 0) { throw PSTraceSource.NewArgumentException(nameof(patch)); } @@ -568,12 +569,12 @@ public static implicit operator Version(SemanticVersion semver) public int Patch { get; } /// - /// PreReleaseLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + /// PreReleaseLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. /// public string PreReleaseLabel { get; } /// - /// BuildLabel position in the SymVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + /// BuildLabel position in the SemVer string 'major.minor.patch-PreReleaseLabel+BuildLabel'. /// public string BuildLabel { get; } @@ -643,7 +644,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) string preLabel = null; string buildLabel = null; - // We parse the SymVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'. + // We parse the SemVer 'version' string 'major.minor.patch-PreReleaseLabel+BuildLabel'. var dashIndex = version.IndexOf('-'); var plusIndex = version.IndexOf('+'); @@ -729,7 +730,7 @@ private static bool TryParseVersion(string version, ref VersionResult result) } if (preLabel != null && !Regex.IsMatch(preLabel, LabelUnitRegEx) || - (buildLabel != null && !Regex.IsMatch(buildLabel, LabelUnitRegEx))) + (buildLabel != null && !Regex.IsMatch(buildLabel, BuildUnitRegEx))) { result.SetFailure(ParseFailureKind.FormatException); return false; @@ -804,7 +805,7 @@ public int CompareTo(object version) /// /// Implement . - /// Meets SymVer 2.0 p.11 https://semver.org/ + /// Meets SemVer 2.0 p.11 https://semver.org/ /// public int CompareTo(SemanticVersion value) { @@ -820,7 +821,7 @@ public int CompareTo(SemanticVersion value) if (Patch != value.Patch) return Patch > value.Patch ? 1 : -1; - // SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). + // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). return ComparePreLabel(this.PreReleaseLabel, value.PreReleaseLabel); } @@ -837,7 +838,7 @@ public override bool Equals(object obj) /// public bool Equals(SemanticVersion other) { - // SymVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). + // SemVer 2.0 standard requires to ignore 'BuildLabel' (Build metadata). return other != null && (Major == other.Major) && (Minor == other.Minor) && (Patch == other.Patch) && string.Equals(PreReleaseLabel, other.PreReleaseLabel, StringComparison.Ordinal); @@ -906,7 +907,7 @@ public override int GetHashCode() private static int ComparePreLabel(string preLabel1, string preLabel2) { - // Symver 2.0 standard p.9 + // SemVer 2.0 standard p.9 // Pre-release versions have a lower precedence than the associated normal version. // Comparing each dot separated identifier from left to right // until a difference is found as follows: diff --git a/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 b/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 index c6fbc6af969..82cc425cd6f 100644 --- a/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 +++ b/test/powershell/engine/Basic/SemanticVersion.Tests.ps1 @@ -267,4 +267,105 @@ Describe "SemanticVersion api tests" -Tags 'CI' { { $PSVersionTable.PSVersion | Format-Table | Out-String } | Should -Not -Throw } } + + Context 'Semver official tests' { + BeforeAll { + $valid = @' +0.0.4 +1.2.3 +10.20.30 +1.1.2-prerelease+meta +1.1.2+meta +1.1.2+meta-valid +1.0.0-alpha +1.0.0-beta +1.0.0-alpha.beta +1.0.0-alpha.beta.1 +1.0.0-alpha.1 +1.0.0-alpha0.valid +1.0.0-alpha.0valid +1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay +1.0.0-rc.1+build.1 +2.0.0-rc.1+build.123 +1.2.3-beta +10.2.3-DEV-SNAPSHOT +1.2.3-SNAPSHOT-123 +1.0.0 +2.0.0 +1.1.7 +2.0.0+build.1848 +2.0.1-alpha.1227 +1.0.0-alpha+beta +1.2.3----RC-SNAPSHOT.12.9.1--.12+788 +1.2.3----R-S.12.9.1--.12+meta +1.2.3----RC-SNAPSHOT.12.9.1--.12 +1.0.0+0.build.1-rc.10000aaa-kk-0.1 +1.0.0-0A.is.legal +'@ + + $validVersions = @() + foreach ($version in $valid.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)) { + $validVersions += @{version = $version} + } + + $invalid = @' +1 +1.2 +1.2.3-0123 +1.2.3-0123.0123 +1.1.2+.123 ++invalid +-invalid +-invalid+invalid +-invalid.01 +alpha +alpha.beta +alpha.beta.1 +alpha.1 +alpha+beta +alpha_beta +alpha. +alpha.. +beta +1.0.0-alpha_beta +-alpha. +1.0.0-alpha.. +1.0.0-alpha..1 +1.0.0-alpha...1 +1.0.0-alpha....1 +1.0.0-alpha.....1 +1.0.0-alpha......1 +1.0.0-alpha.......1 +01.1.1 +1.01.1 +1.1.01 +1.2 +1.2.3.DEV +1.2-SNAPSHOT +1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 +1.2-RC-SNAPSHOT +-1.0.3-gamma+b7718 ++justmeta +9.8.7+meta+meta +9.8.7-whatever+meta+meta +99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12 +'@ + + $invalidVersions = @() + foreach ($version in $invalid.Split([Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries)) { + $invalidVersions += @{version = $version} + } + } + + It 'Should parse valid versions: ' -TestCases $validVersions { + param($version) + $v = [semver]"$version" + $v.ToString() | Should -Be $version + } + + It 'Should not parse invalid versions: ' -TestCases $invalidVersions { + { [semver]"$version" } | Should -Throw + } + } + }