-
Notifications
You must be signed in to change notification settings - Fork 473
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
Add support for range and comparators comparison #187
Conversation
* Check if two ranges intersect * Check if one comparator satisfies a range * Check if two comparators intersect
semver.js
Outdated
((compA.operator === '<=' || compA.operator === '<') && (compB.operator === '>=' || compB.operator === '>'))); | ||
} | ||
|
||
exports.comparatorSatisfiesRange = comparatorSatisfiesRange; |
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.
function names are a little overly verbose/long for my tastes. but looks like good functionality
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.
Similarly; if this was a prototype method on Comparator it could just be satisfiesRange
?
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.
Sure, I think I was following the toComparators
logic but I think I like it more this way too.
@shellscape I am completely open for alternatives. Let me know if you have any suggestions. |
semver.js
Outdated
@@ -793,6 +793,56 @@ function toComparators(range, loose) { | |||
}); | |||
} | |||
|
|||
exports.comparatorsIntersect = comparatorsIntersect; | |||
function comparatorsIntersect(compA, compB, loose, platform) { |
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.
Is this better as a standalone function, or as a prototype method on Comparator?
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.
👍
semver.js
Outdated
exports.comparatorsIntersect = comparatorsIntersect; | ||
function comparatorsIntersect(compA, compB, loose, platform) { | ||
compA = new Comparator(compA, loose); | ||
compB = new Comparator(compB, loose); |
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.
Reassigning function arguments deopts in v8; could you use new variables for these?
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.
👍
semver.js
Outdated
((compA.operator === '<=' || compA.operator === '<') && (compB.operator === '>=' || compB.operator === '>'))); | ||
} | ||
|
||
exports.comparatorSatisfiesRange = comparatorSatisfiesRange; |
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.
Similarly; if this was a prototype method on Comparator it could just be satisfiesRange
?
semver.js
Outdated
} | ||
|
||
exports.rangesIntersect = rangesIntersect; | ||
function rangesIntersect(rangeA, rangeB, loose, platform) { |
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.
And if this was a prototype method on Range it could just be intersects
?
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.
👍
test/index.js
Outdated
['<1.0.0 >2.0.0', '>0.0.0', true], | ||
['<1.0.0 >2.0.0', '>1.4.0 <1.6.0', false], | ||
['<1.0.0 >2.0.0', '>1.4.0 <1.6.0 || 2.0.0', false], | ||
['<1.0.0 >=2.0.0', '2.1.0', false], |
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.
Could you add a test case for a single version returning true?
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.
👍
test/index.js
Outdated
var range2 = v[1]; | ||
var expect = v[2]; | ||
var actual = semver.rangesIntersect(range1, range2); | ||
t.equal(actual, expect); |
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.
Could we also assert that if you flip the range order that it provides the same 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.
👍
['<=1.3.0', '<=1.3.0', true], | ||
['>1.3.0', '<=1.3.0', false], | ||
['>=1.3.0', '<1.3.0', false], | ||
// Opposite matching directions |
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 could instead be done in the iterator function, for every test, no?
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 understanding exactly what you mean here. Can you extend a bit?
Also, should I amend my previous commit or should I create new commits to fix the requested changes?
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 mean, line 752 asks if comparator1 intersects comparator2; could you also add an assertion after that asking if comparator2 intersects comparator1?
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 think amending and force pushing is always fine; unless one of this repo's collabs says different.
semver.js
Outdated
@@ -704,6 +704,42 @@ Comparator.prototype.test = function(version) { | |||
return cmp(version, this.operator, this.semver, this.loose); | |||
}; | |||
|
|||
Comparator.prototype.intersects = function(compOrStr, loose, platform) { | |||
var rangeTmp; | |||
var comp = new Comparator(compOrStr, loose); |
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 be bad if this only took a Comparator? That would make the API more explicit and delegate more decisions to the user.
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 ok with this change, but I actually do not think the user makes more decisions. Since we allow the method to receive either a comparator or a string they can do all the same decisions if they want.
But the API is definitely more explicit if we force a Comparator.
Usually I am a bit more defensive and that is why I usually try to handle all the weird possibilities. But if this was Typescript it would make much more sense to receive only the comparator.
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 don't think typescript has anything to do with it; it's nicer to have monomorphic APIs with or without types. By "decisions" I mean, the comparator and the range having the same loose
setting, for example - forcing the user to make that choice means they can never be ignorant of the default.
semver.js
Outdated
|
||
Comparator.prototype.satisfiesRange = function(rangeOrStr, loose, platform) { | ||
var comp = this; | ||
var range = new Range(rangeOrStr, loose, platform); |
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 question here
semver.js
Outdated
@@ -783,6 +819,16 @@ Range.prototype.parseRange = function(range) { | |||
return set; | |||
}; | |||
|
|||
Range.prototype.intersects = function(rangeOrStr, loose, platform) { | |||
var range = new Range(rangeOrStr, loose, platform); |
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.
And here
['<=1.3.0', '<=1.3.0', true], | ||
['>1.3.0', '<=1.3.0', false], | ||
['>=1.3.0', '<1.3.0', false], | ||
// Opposite matching directions |
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 mean, line 752 asks if comparator1 intersects comparator2; could you also add an assertion after that asking if comparator2 intersects comparator1?
['<=1.3.0', '<=1.3.0', true], | ||
['>1.3.0', '<=1.3.0', false], | ||
['>=1.3.0', '<1.3.0', false], | ||
// Opposite matching directions |
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 think amending and force pushing is always fine; unless one of this repo's collabs says different.
@ljharb I think I made all the changes you suggested in this last commit. Take a look when you have some time. |
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 think this looks great!
I would strongly prefer much more defensive throwing to check the types of arguments at runtime, but il leave insisting on that to the collaborators.
Hopefully this gets merged and released soon!
semver.js
Outdated
return satisfies(comp.semver, rangeTmp, loose, platform); | ||
} | ||
// Same direction increasing | ||
return ((this.operator === '>=' || this.operator === '>') && (comp.operator === '>=' || comp.operator === '>')) || |
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 like making this one statement makes it a bit more confusing to read; what do you think about using if/else 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.
Can you provide an example?
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.
Sure - maybe just separate variables would help. Here's a subset:
var sameDirectionIncreasing = (this.operator === '>=' || this.operator === '>') && (comp.operator === '>=' || comp.operator === '>');
var sameDirectionDecreasing = (this.operator === '<=' || this.operator === '<') && (comp.operator === '<=' || comp.operator === '<');
var sameSemver = this.semver.raw === comp.semver.raw;
var differentDirectionsInclusive = (this.operator === '>=' || this.operator === '<=') && (comp.operator === '>=' || comp.operator === '<=');
return sameDirectionIncreasing || sameDirectionDecreasing || (sameSemver && differentDirectionsInclusive);
something like that? the long chain of boolean operations makes it hard for me to read.
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.
Sounds like a nice improvement.
When you say "I would strongly prefer much more defensive throwing to check the types of arguments at runtime, but il leave insisting on that to the collaborators", like what check if |
@rtfpessoa exactly like that - i'd do In other words, if I'm passing the wrong kind of argument, I want to be alerted to that fact as soon and explicitly as possible. |
Just did both changes except for the |
5da850d
to
7a525c5
Compare
Also, I added a couple more tests for the parameter checks and fixed the format issues. |
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.
Thanks! Much cleaner imo :-)
This seems super useful! It just needs documentation to land. |
Awesome! If @rtfpessoa can't find time to write it soon, I'll try to find time to write some :-) |
@iarna can you extend a bit on what do you mean by documentation? Edit: Do you mean README docs? If that is it, I completely missed it, will do it now. |
@@ -2,3 +2,4 @@ | |||
.*.swp | |||
coverage/ | |||
.nyc_output/ | |||
.idea |
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.
Generally this belongs in a global git config, rather than polluting specific git repos with IDE-specific knowledge (nbd tho)
@rtfpessoa Yes, that's exactly what I was looking for! |
@iarna, not sure if you noticed but I already did it. Le me know if you need any other changes for the merge. |
looking forward for the landing of this PR 🙏 |
Really excited to be able to use this feature! Are there any updates on this PR? |
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 seems like it works, and Range intersection is definitely something that people ask for often. Thanks for taking the lead on this, and sorry for the delays in reviewing.
Some changes requested, but I think we can get this landed relatively quickly. Thanks!
semver.js
Outdated
return satisfies(comp.semver, rangeTmp, loose, platform); | ||
} | ||
|
||
var sameDirectionIncreasing = (this.operator === '>=' || this.operator === '>') && (comp.operator === '>=' || comp.operator === '>'); |
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.
These really long lines are very hard to read.
Can you please break this up into separate stanzas? There should be no lines longer than 80 chars under any circumstances. 70 is an even better limit.
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.
semver.js
Outdated
oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; | ||
}; | ||
|
||
Comparator.prototype.satisfiesRange = function(range, loose, platform) { |
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 is the "platform" variable being passed around here? It doesn't seem like it's ever used for anything.
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.
Ported this from the Ruby version and somehow did not notice platform did not exist here.
README.md
Outdated
@@ -329,6 +329,10 @@ strings that they parse. | |||
(`major`, `premajor`, `minor`, `preminor`, `patch`, `prepatch`, or `prerelease`), | |||
or null if the versions are the same. | |||
|
|||
### Comparators | |||
* `intersects(comparator)`: Return true if the comparators intersect | |||
* `satisfiesRange(range)`: Return true if the comparator intersects with any |
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 naming of this is a bit weird. You wouldn't normally say that a comparator "satisfies" a range. A range is satisfied by a single thing which has a specific version, not by a comparator, which is like a mini-range. Also, a comparator like <=1.4.5
would return true for this method, given the Range >=1.3.0
, even though many versions allowed by that comparator are not satisfying answers for the Range. We can only talk about intersections and unions with ranges (and their building blocks, Comparators) because they are sets, not discrete atomic items.
Having a comparator.intersects(comparator)
method seems like a building block towards having range intersection. People generally don't interact with comparators directly. Having (effectively) comparator.intersects(range)
is a bit odd, and using the language of "satisfying" makes it odder.
It seems like what you'd really want in most cases is range.intersects(range)
. It's probably better to leave aside things that don't get us closer to 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.
I think this is probably addressed by calling it comparator.intersectsRange()
? Or even cleaner, make Comparator.intersects
take either a Range or Comparator as an argument.
@isaacs will try to do this soon. |
@isaacs let me know if this was what you asked. I removed the confusing method and exposed only the two intersections between the same type. |
var sameDirectionDecreasing = | ||
(this.operator === '<=' || this.operator === '<') && | ||
(comp.operator === '<=' || comp.operator === '<'); | ||
var sameSemVer = this.semver.raw === comp.semver.raw; |
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 think what you're after isn't semver.raw
here, but semver.version
. Consider this:
> r2 = new s.Range('>=v1.2.3')
Range {
loose: undefined,
raw: '>=v1.2.3',
set: [ [ [Object] ] ],
range: '>=1.2.3' }
> r = new s.Range('<=1.2.3')
Range {
loose: undefined,
raw: '<=1.2.3',
set: [ [ [Object] ] ],
range: '<=1.2.3' }
> r.intersects(r2)
false
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.
Yep, that fixes it. I'll include it in the merge, no worries.
Landed on master and published as v5.4.0. Also added a top-level |
Yay, thanks! |
Fixes #178