Skip to content

Conversation

@iSazonov
Copy link
Collaborator

@iSazonov iSazonov commented Oct 6, 2017

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

@daxian-dbw daxian-dbw requested review from PaulHigin and removed request for BrucePay and vors November 1, 2017 04:25
@daxian-dbw
Copy link
Member

@JamesWTruher @PaulHigin Can you please review this PR?

Copy link
Contributor

@PaulHigin PaulHigin left a 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: The patch version

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

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.

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

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 :)

Copy link
Collaborator Author

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.

Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

Copy link
Collaborator Author

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()

Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

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 :)

Copy link
Contributor

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().

Copy link
Contributor

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.

Copy link
Collaborator Author

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.
@iSazonov iSazonov force-pushed the fix-semanticversion branch from 53ea5b7 to 9571e06 Compare November 5, 2017 11:20
@iSazonov
Copy link
Collaborator Author

iSazonov commented Nov 5, 2017

I've rebased to resolve a merge conflict.

@daxian-dbw daxian-dbw requested a review from anmenaga November 6, 2017 16:34
@daxian-dbw
Copy link
Member

@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 '+'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'-' ot contains

Typo here.

Copy link
Collaborator Author

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\-\.]*?$";
Copy link
Member

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.

Copy link
Member

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?

Copy link
Collaborator Author

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; }
Copy link
Member

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?

Copy link
Collaborator Author

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>

Copy link
Member

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?

Copy link
Collaborator Author

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;
Copy link
Member

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?

Copy link
Collaborator Author

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();
Copy link
Member

@daxian-dbw daxian-dbw Nov 6, 2017

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?

Copy link
Collaborator Author

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?

Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

@daxian-dbw daxian-dbw Nov 8, 2017

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; }
Copy link
Member

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. "

Copy link
Collaborator Author

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.

@SteveL-MSFT SteveL-MSFT added this to the 6.0.0-RC milestone Nov 7, 2017
.AddPropertyColumn("Major")
.AddPropertyColumn("Minor")
.AddPropertyColumn("Patch")
.AddPropertyColumn("PreLabel")
Copy link
Member

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'.

Copy link
Collaborator Author

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")
Copy link
Member

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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very long 😕

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe PSSemVerPreReleaseLabel?

Copy link
Member

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.

Copy link
Collaborator Author

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)
Copy link
Member

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.

Copy link
Collaborator Author

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";
Copy link
Member

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.

Copy link
Collaborator Author

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) {
Copy link
Member

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.

Copy link
Collaborator Author

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);
}
Copy link
Member

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;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@daxian-dbw
Copy link
Member

I still have a concern about the GetHashCode() method. See my new comment at #5037 (comment)

@daxian-dbw
Copy link
Member

@SteveL-MSFT This PR changes the public APIs, including:

  • Add a public constructions public SemanticVersion(int major, int minor, int patch, string preLabel, string buildLabel)
  • Remove public property Label and add two public properties PreReleaseLabel and BuildLabel.
  • Add a public method public static int Compare(SemanticVersion versionA, SemanticVersion versionB)

Do you think it needs the committee review?

td252.Members.Add("ToString",
new ScriptMethodData(@"ToString", GetScriptBlock(@"
$suffix = """"
if (![String]::IsNullOrEmpty($this.PSSemanticVersionPreLabel))
Copy link
Member

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.

@SteveL-MSFT
Copy link
Member

@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.

@iSazonov
Copy link
Collaborator Author

iSazonov commented Nov 7, 2017

SemanticVersion API is new in PowerShell Core so until RTM we can change it free if we need.

@daxian-dbw
Copy link
Member

I'm fine with the API changes in this PR.

@iSazonov iSazonov force-pushed the fix-semanticversion branch 2 times, most recently from 1f45175 to c1a9945 Compare November 8, 2017 09:22
Copy link
Member

@daxian-dbw daxian-dbw left a 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. parameter names.

@SteveL-MSFT SteveL-MSFT changed the title Make SemanticVersion compatible with SymVer 2.0 Make SemanticVersion compatible with SemVer 2.0 Nov 8, 2017
@daxian-dbw
Copy link
Member

The test failure in AppVeyor is a known issue and will be fixed by #5379
Travis CI passed, but it's not updated here.

@iSazonov
Copy link
Collaborator Author

iSazonov commented Nov 9, 2017

😕 Sorry, I forgot to set VS Code case-sensitive flag to search and replace.

@daxian-dbw
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants