-
Notifications
You must be signed in to change notification settings - Fork 633
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
cleanup semver #3948
Comments
I mostly agree. But I think these functions should probably have a
I agree.
I weakly agree. However, doing so would require the dev to learn these comparison concepts. Happy to hear other opinions.
I agree. |
I agree but the current
I agree to gather more opinions. |
@iuioiua I have a question about |
|
I think this would be not too hard to achieve because we can write some /**
* @deprecated (...)
* use ```ts
compare(...) > 0
``` instead.
*/ or similar into the deprecation note. |
|
These sound fine how they are to me.
I agree.
I agree.
What if we had a Before: import { gt, parse } from "https://deno.land/std/semver/mod.ts"
const s0 = parse(v0);
const s1 = parse(v1);
gt(s0, s1); After: import { SemVer } from "https://deno.land/std/semver/mod.ts"
const s0 = new SemVer(v0);
const s1 = new SemVer(v1);
s0 > s1; |
I am not for a SemVer class approach. This is how the implementation initially was and we moved away because a semver can be represented by a native object and we got overlaps and special case handlings for class instances as well as class instantiation overhead for the simplest things. |
@iuioiua I think it would not be a big deal to change from eq(s0, s1)
neq(s0, s1)
gt(s0, s1)
gte(s0, s1)
lt(s0, s1)
lte(s0, s1) to compare(s0, s1) === 0
compare(s0, s1) !== 0
compare(s0, s1) > 0
compare(s0, s1) >= 0
compare(s0, s1) < 0
compare(s0, s1) <= 0 with enough time to switch. How should we go about it? |
I feel two ways about this. On one hand, I like a smaller, simpler API. It'd reduce engineering costs. Also, this makes comparisons more "organic" and closer to comparing numbers, which I like. However, you can argue that this might be a slightly poorer DX. I'd be keen to hear other's opinions. |
I posted this issue on discord for more opinions. In addition, removing them would also solve the issue of their current names, since std functions should not use abbreviations and acronyms if possible. In std we have multiple other mods who use |
I like the generic compare function because it would allow us to use it for |
@marvinhagemeister, do you think I'm leaning more in favour of this change now. I do still understand those that want to use |
I'd add the |
@marvinhagemeister that is exactly what we have now: We have |
On the dedicated comparator functions:
I agree with some of the things that were already posted:
Yet I also agree that the functions might not belong as top-level exports. I think if they are removed, that examples should be added to the /**
* Compare two semantic version objects.
*
* Returns `0` if `v1 === v2`, or `1` if `v1` is greater, or `-1` if `v2` is
* greater.
*
* Sorts in ascending order if passed to `Array.sort()`.
+ *
+ * The number returned by `compare` can then be compared to `0` in order to
+ * determine some common cases:
+ * - `compare(a, b) === 0` a equal to b
+ * - `compare(a, b) !== 0` a not equal to b
+ * - `compare(a, b) < 0` a less than b
+ * - `compare(a, b) <= 0` a less than or equal to b
+ * - `compare(a, b) > 0` a greater than b
+ * - `compare(a, b) >= 0` a greater than or equal to b
*/
export function compare(
s0: SemVer,
s1: SemVer,
): 1 | 0 | -1 { Aside: After looking at the documentation, I also think that using the convention of If we decide to keep them, I wonder if they could become methods on the export const compare:
& ((a: SemVer, b: SemVer) => 1 | 0 | -1)
& Record<
| "eq" // or "equal", etc.
| "gt" // or "greaterThan", etc.
| "gte" // or "greaterThanOrEqual", etc.
| "lt" // or "lessThan", etc.
| "lte" // or "lessThanOrEqual", etc.
| "neq", // or "notEqual", etc.
(a: SemVer, b: SemVer) => boolean
> = … const a = { major: 2 } as SemVer;
const b = { major: 1 } as SemVer;
assert(compare(a, b) === 1);
assert(compare.eq(a, b) === false);
assert(compare.gt(a, b) === true);
assert(compare.gte(a, b) === true);
assert(compare.lt(a, b) === false);
assert(compare.lte(a, b) === false);
assert(compare.neq(a, b) === true); See this TypeScript Playground example for a working demonstration. |
While I like the look of that approach, I am not sure if it is good practice to extend a function like that. |
I'm 50/50 on keeping |
I had another look at the mod. There are also |
Hm... Maybe. I prefer a smaller API. It'd be easier to manage and make high-quality. Low-value abstractions are not attractive to me. Happy to hear other opinions. |
@iuioiua How about we deprecate them and reintroduce them with proper names at a later point if there are good arguments to have them? I could imagine, that nobody would miss them if they are gone and we provide good examples with |
Another inconsistency I found: export const OPERATORS = [
"",
"=",
"==",
"===",
"!==",
"!=",
">",
">=",
"<",
"<=",
] as const; Should we support const COMPARATOR = "(?:<|>)?=?"; this means @iuioiua WDYT? |
I'm cool with that. It'd be another step away from node-semver's API, but IMO, that's a good thing. Yoshiya's yet to comment on the idea, so no guarantee it'll go through.
Interesting. I think these operators are included for "completeness" (see It's funny. The more we dive into the semver API, the more API bloat we find. |
Note: This would also make it easier to define, what exactly a interface SemVer {
major: number;
minor: number;
patch: number;
prerelease?: (string | number)[];
build?: string[];
}
/**
* a SemVer with an operator
* @example "<1.2.3" => { operator: "<", major: 1, minor: 2, patch: 3 }
*/
interface Comparator extends SemVer {
operator: "<" | "<=" | ">" | ">=";
}
/**
* A Range is either one or two Comparators, describing a minimum and/or maximum version.
* @example "<2.0.0" => [ { operator: "<", major: 2, minor: 0, patch: 0 } ]
* @example ">=1.0.0 <2.0.0" => [ { operator: ">=", major: 1, minor: 0, patch: 0 }, { operator: "<", major: 2, minor: 0, patch: 0 } ]
*/
type Range = [Comparator] | [Comparator, Comparator];
/**
* RangeSet are multiple ranges combined with OR.
* @example ">=1.0.0 <2.0.0 || >3.0.0" => [ [ { operator: ">=", major: 1, minor: 0, patch: 0 }, { operator: "<", major: 2, minor: 0, patch: 0 } ], [ { operator: ">", major: 3, minor: 0, patch: 0 } ], ]
*/
type RangeSet = Range[];
|
Another thing: Edit: |
Happy to have a look at a PR that does that. |
|
|
Imo the name compare(s0, s1) // compares including prerelease
compare(s0, s1, { matcher: "prerelease" }) // same as default
compare(s0, s1, { matcher: "build" }) // compare including prerelease and build
compare(s0, s1, { matcher: "version" }) // compare excluding prerelease and build |
I don't like Semver spec also says the below:
I now feel that we should just deprecate |
I think that would be a good solution. WDYT @iuioiua ? |
I agree. However, let's keep in mind that some devs may use |
A weird thing I encountered in So imagine the following:
This returns
So I wonder if that is a bug and why one would not just do switch (hilo) {
case ">":
return gt(version, rangeMax(range));
case "<":
return lt(version, rangeMin(range));
default:
return gt(version, rangeMax(range)) || lt(version, rangeMin(range));
} which seems a weird function to have. |
@timreichen, how does the |
import * as semver from "npm:semver";
console.log(semver.outside("2.5.0", ">= 1.0.0 < 2.0.0 || >=3.0.0 < 4.0.0", "<")); // false
console.log(semver.outside("2.5.0", ">= 1.0.0 < 2.0.0 || >=3.0.0 < 4.0.0", ">")); // false This was directly ported, so it works as the current implementation. This seems to be a bug in my view, because |
Yeah, that looks like a bug to me too. Even if |
I looked once more over |
I'm for the removal. I saw it in my recent cleanups and thought the same thing. |
All the functions take For improved ergonomics, could some (most) of them take string? Eg |
I am against this approach. these functions use semver objects internally, not strings. Let's say you use multiple function calls and pass a string each time: It would need to get parsed with every function call. |
I understand that. But are there many use cases where end-users will be manipulating and passing SemVer objects around? I thought 95% of the time users would just call I was wondering if internally we could use only |
I think each function should do only one task, the one that is in described in the name. In that sense, adding parsing as a task to each function seems really superfluous.
Imo it is trivial to have the user do |
I fully agree with @timreichen and at the same time I think it's our responsibility to document common usage cases for inexperienced users: essentially answers to questions like "Why would I use this?", "How do I x?", etc. |
I agree with your points from a technical perspective. But in practice, taking into account how this specific module will be used in the real world, I'm having difficulty to think of a use case where the user will have a SemVer object before having the string. Currently I'm doing this: greaterOrEqual(parse(Deno.version.deno), parse('1.40.5')) It could be like this: greaterOrEqual(Deno.version.deno, '1.40.5') In the second case I only need to import If we can't think of a use case where the user calls The Anyway, feel free to disagree. I'm happy just to share my points. I trust that you guys will take the best decision 👍. |
I also see your point having convenience for simple use cases, though at the expense of a bit more abstraction and performance. |
@iuioiua I wonder if it would make sense to move all comparator functions inside their counterpart range files, e.g. |
Yep. That makes sense to do. |
It seems that everything here is complete. Thank you for all your input and diligence, @timreichen. |
Background
There are several inconsistencies in the semver mod:
File names and exported functions
The semver mod is not well structured. The file names and functions are not consistent.
range_format.ts
andcomparator_format.ts
uses[*_verb].ts
and*Format()
, butparse_range.ts
andparse_comparator.ts
uses[verb_*].ts
andparse*()
.Another inconsistency is:
parse_comparator.ts
andparse_range.ts
butis_semver_comparator.ts
andis_semver_range.ts
. The_semver
in the name seems obsolete.eq, neq, gt, gte, lt, lte
These functuons are wrapper functions for
compare()
compare the result to 0 with different comparators:return compare(s0, s1) [===|!==|>|>=|<|<=] 0
. These seem too trivial.rcompare
This function calls
compare()
with switched arguments. This seems too trivial.Proposal
File names and exported functions
Rename
[*_verb].ts
to[verb_*].ts
and*Format()
toformat*()
for all filenames and functions and deprecate old functions and remove_semver
from file names and function:rangeFormat()
=>formatRange()
deprecation(semver): renamerangeFormat()
toformatRange()
#4090deprecatedcomparatorFormat()
=>formatComparator()
isSemVerComparator()
=>isComparator()
deprecation(semver): renameisSemVerComparator()
#3957isSemVerRange()
=>isRange()
deprecation(semver): deprecateSemVerRange
, introduceRange
#4161testRange()
=>inRange()
orrangeIncludes()
deprecation(semver): renametestRange()
tosatisfies()
#4364eq, neq, gt, gte, lt, lte
deprecate all these functions deprecation(semver): deprecateeq()
,neq()
,lt()
,lte()
,gt()
andgte()
#4048eq()
,neq()
,lt()
,lte()
,gt()
andgte()
#4083discuss if these functions should be put incompare.ts
rcompare
rcompare()
#3958Others
Comparator
necessity Discussion(semver): Necessity ofComparator
#4047OPERATORS
("", "==", "===", "!==") deprecation(semver): deprecate==
,===
,!==
, and""
operators #4271compareBuild()
deprecation(semver): deprecatecompareBuild()
#4088outside()
deprecation(semver): deprecateoutside()
#4185@iuioiua @kt3k WDYT?
The text was updated successfully, but these errors were encountered: