-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Make SemanticVersion compatible with SemVer 2.0 #5037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@JamesWTruher @PaulHigin Can you please review this PR? |
PaulHigin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not quite finished with the review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: The patch version
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to explicitly set properties to null since CLR automatically initializes fields to zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only thought about symmetry and better readability.
Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these static strings also be const string? They are never reassigned so there is no point in allocating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you include examples of allowed label strings in the comment? I don't read regex very well :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Complex RegEx is a puzzle for me too. I found some sample RegEx strings for SymVer but after testing I concluded that they all have bugs.
So I split the SymVer regex string on some string (s_VersionSansRegEx, s_LabelRegEx, s_LabelUnitRegEx). This slightly increased the code but helped to avoid bugs.
Comment added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Group.Value always returns an empty string for no matches. Should the PreLable/BuildLable properties be initialized to empty string rather than null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that we always check strings for NullOrEmpty so I think we are Ok as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should do this preLabel, buildLabel regex validation in the SemanticVersion constructor. That way we can prevent malformed SemanticVersion objects from being created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder too why I checked only first char in constructors :-) I wonted to be fast. We should use RegEx.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we still need this check, otherwise v1.CompareTo will throw null reference exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by new method Compare()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel we still need this check, otherwise v1.CompareTo will throw null reference exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check v1 for null value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should check v1 for null value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment that PreLabel takes lower precedence when normal versions are equal, per spec. This logic always confuses me until I remember that :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually this PreLabel null check here can be removed since it is duplicated in ComparePreLabel().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But please add the comment in ComparePreLabel() method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate checks was removed.
Comment was added.
Replace static strings with const. Add strong checks in constructors. Add comments. Add static Compare() and fix comparisions operators.
53ea5b7 to
9571e06
Compare
|
I've rebased to resolve a merge conflict. |
|
@anmenaga Can you please also review this PR? Thanks! |
| /// <param name="preLabel">The preLabel for the version</param> | ||
| /// <param name="buildLabel">The buildLabel for the version</param> | ||
| /// <exception cref="FormatException"> | ||
| /// If <paramref name="preLabel"/> starts with '-' ot contains '+'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'-' ot contains
Typo here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the comments because the code was changed previously.
| { | ||
| private const string s_VersionSansRegEx = @"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?$"; | ||
| private const string s_LabelRegEx = @"^((?<preLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?<buildLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?$"; | ||
| private const string s_LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*?$"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable names should be changed. s_ indicates static private variables, but they are const variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-.]*?$
Since you are matching to the end of line, is the question mark ? (lazy matching) needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s_ was removed.
? was removed - I think it is bug in any case.
| /// PreLabel position in the SymVer string 'major.minor.patch-PreLabel+BuildLabel'. | ||
| /// </summary> | ||
|
|
||
| public string PreLabel { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should just the full name PrereleaseLabel for the public property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced with PreReleaseLabel - it looks more readable.
| /// The last component in a SemanticVersion - may be null if not specified. | ||
| /// PreLabel position in the SymVer string 'major.minor.patch-PreLabel+BuildLabel'. | ||
| /// </summary> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please remove the extra new line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
| /// </summary> | ||
| public static int Compare(SemanticVersion versionA, SemanticVersion versionB) | ||
| { | ||
| if (versionA == null) return -1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about versionA and versionB are both null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
Fixed.
| Minor.GetHashCode(), | ||
| Patch.GetHashCode(), | ||
| Label == null ? 0 : Label.GetHashCode()); | ||
| return versionString.GetHashCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
versionString could be null when running this method.
Besides, if we use the hashcode of versionString for the SemanticVersion, we would have a hashcode collision, would that be a problem? Why not continue to use Utils.CombineHashCodes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
null - fixed.
versionString is relatively short string - why you believe that CoreFX implementation of String.GetHashCode() so bad that we should make more complex hashcode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean is the hashcode of a SemanticsVersion instance would be the same of the hashcode of a string (the versionString), so there is a collision in terms that 2 different objects have the same hashcode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the docs I don't found that the hash must be different for different objects of different types. It seems we follow the docs - it will be work well with Dictionary and Hashtable types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm just overthinking it. Let's keep it as is. #Close
| } | ||
| else | ||
| { | ||
| if (isNumber1) { return -1; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if you can add comment here saying that "Numeric identifiers always have lower precedence than non-numeric identifiers. "
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copy-paste all text from the standard.
| .AddPropertyColumn("Major") | ||
| .AddPropertyColumn("Minor") | ||
| .AddPropertyColumn("Patch") | ||
| .AddPropertyColumn("PreLabel") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The property name has changed to 'PrereleaseLabel'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| .AddPropertyColumn("Minor") | ||
| .AddPropertyColumn("Build") | ||
| .AddPropertyColumn("Revision") | ||
| .AddPropertyColumn("PSSemanticVersionPreLabel") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to change this property name to PSSemanticVersionPrereleaseLabel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very long 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe PSSemVerPreReleaseLabel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks good to me, especially semver is the type accelerator for SemanticVersion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| /// If <paramref name="preLabel"/> don't match 'LabelUnitRegEx'. | ||
| /// If <paramref name="buildLabel"/> don't match 'LabelUnitRegEx'. | ||
| /// </exception> | ||
| public SemanticVersion(int major, int minor, int patch, string preLabel, string buildLabel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we rename preLabel to prereleaseLabel too? It's the parameter of a public method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed.
| private const string VersionSansRegEx = @"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?$"; | ||
| private const string LabelRegEx = @"^((?<preLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?(\+(?<buildLabel>[0-9A-Za-z][0-9A-Za-z\-\.]*))?$"; | ||
| private const string LabelUnitRegEx = @"^[0-9A-Za-z][0-9A-Za-z\-\.]*$"; | ||
| private const string PreLabelPropertyName = "PSSemanticVersionPreLabel"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. We probably should use PSSemanticVersionPrereleaseLabel, since it's for a public property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| /// </summary> | ||
| public static int Compare(SemanticVersion versionA, SemanticVersion versionB) | ||
| { | ||
| if (versionA != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor comment: it's better to follow the same open curly brace style in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| if (versionB != null) | ||
| { | ||
| return versionA.CompareTo(versionB); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instance method CompareTo handles null value in it, so this versionB != null check is not needed. It can just be:
if (versionA != null)
{
return versionA.CompareTo(versionB);
}
if (versionB != null)
{
return -1;
}
return 0;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
I still have a concern about the |
|
@SteveL-MSFT This PR changes the public APIs, including:
Do you think it needs the committee review? |
| td252.Members.Add("ToString", | ||
| new ScriptMethodData(@"ToString", GetScriptBlock(@" | ||
| $suffix = """" | ||
| if (![String]::IsNullOrEmpty($this.PSSemanticVersionPreLabel)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be changed if the property name is changed.
|
@daxian-dbw the general guidance is that for small api changes, the code review process is sufficient. However, any maintainer can request Committee review if needed or if the contributors for the code review can't come to an agreement. |
|
SemanticVersion API is new in PowerShell Core so until RTM we can change it free if we need. |
|
I'm fine with the API changes in this PR. |
1f45175 to
c1a9945
Compare
daxian-dbw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks to me you just simply replaced preLabel with PreReleaseLabel and didn't care about the local variables and parameter names. Local variable names and parameter names should start with a lower case letter. I will fix it for you.
.vscode/tasks.json
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A local variable name should start with a lower-case letter.
In fact, I think you can continue to use preLabel for any non-public members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, I think it's OK to use preLabel here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Local variable should start with lower case letter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. parameter names.
c1a9945 to
2836c8b
Compare
|
The test failure in AppVeyor is a known issue and will be fixed by #5379 |
|
😕 Sorry, I forgot to set VS Code case-sensitive flag to search and replace. |
|
The macOS CI was shown as passed yesterday after clicking the Travis CI "Details" link. I have no idea why it's shown as canceled today. Now we seem to have a problem with macOS CI -- it takes forever to start. Given that I know the Travis CI previously passed, I will merge this PR. |
Fix #5004
Fix description
Made compatible with SymVer 2.0 http://semver.org/ (p.10)
Fixed comparisons
Refactored tests
Added new tests
Additional information
Useful sample https://github.com/maxhauser/semver/blob/master/src/Semver/SemVersion.cs
Related #2983