From 430ec373aa42c5120e9dead43ba4c684135744a3 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 10 Jan 2024 21:16:46 +0100 Subject: [PATCH 1/8] initial commit --- semver/comparator_format.ts | 2 +- semver/comparator_intersects.ts | 8 ++++---- semver/constants.ts | 2 ++ semver/format_range.ts | 8 +++++--- semver/gtr.ts | 4 ++-- semver/is_range.ts | 22 ++++++++++++++++++++++ semver/is_range_test.ts | 20 ++++++++++++++++++++ semver/is_semver_range.ts | 6 ++++-- semver/ltr.ts | 4 ++-- semver/max_satisfying.ts | 4 ++-- semver/min_satisfying.ts | 4 ++-- semver/outside.ts | 4 ++-- semver/parse_comparator.ts | 2 +- semver/parse_range.ts | 7 ++++--- semver/range_format.ts | 4 ++-- semver/range_intersects.ts | 24 +++++++++++++++--------- semver/range_max.ts | 11 +++++++---- semver/range_min.ts | 11 +++++++---- semver/test_comparator.ts | 2 +- semver/test_range.ts | 13 ++++++++----- semver/try_parse_range.ts | 4 ++-- semver/types.ts | 19 ++++++++++++++----- 22 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 semver/is_range.ts create mode 100644 semver/is_range_test.ts diff --git a/semver/comparator_format.ts b/semver/comparator_format.ts index 2725fd6165e9..ec37f3d5698d 100644 --- a/semver/comparator_format.ts +++ b/semver/comparator_format.ts @@ -12,5 +12,5 @@ import { format } from "./format.ts"; */ export function comparatorFormat(comparator: Comparator): string { const { semver, operator } = comparator; - return `${operator}${format(semver)}`; + return `${operator}${format(semver ?? comparator)}`; } diff --git a/semver/comparator_intersects.ts b/semver/comparator_intersects.ts index 98a42addc233..6f7deb4c7eb8 100644 --- a/semver/comparator_intersects.ts +++ b/semver/comparator_intersects.ts @@ -16,10 +16,10 @@ export function comparatorIntersects( c0: Comparator, c1: Comparator, ): boolean { - const l0 = comparatorMin(c0.semver, c0.operator); - const l1 = comparatorMax(c0.semver, c0.operator); - const r0 = comparatorMin(c1.semver, c1.operator); - const r1 = comparatorMax(c1.semver, c1.operator); + const l0 = comparatorMin(c0.semver ?? c0, c0.operator); + const l1 = comparatorMax(c0.semver ?? c0, c0.operator); + const r0 = comparatorMin(c1.semver ?? c1, c1.operator); + const r1 = comparatorMax(c1.semver ?? c1, c1.operator); // We calculate the min and max ranges of both comparators. // The minimum min is 0.0.0, the maximum max is ANY. diff --git a/semver/constants.ts b/semver/constants.ts index 745e3c987012..0c4c95d12aab 100644 --- a/semver/constants.ts +++ b/semver/constants.ts @@ -67,6 +67,7 @@ export const ANY: SemVer = { */ export const ALL: Comparator = { operator: "", + ...ANY, semver: ANY, }; @@ -75,5 +76,6 @@ export const ALL: Comparator = { */ export const NONE: Comparator = { operator: "<", + ...MIN, semver: MIN, }; diff --git a/semver/format_range.ts b/semver/format_range.ts index ac2fb98d02c2..a56b91af4a5a 100644 --- a/semver/format_range.ts +++ b/semver/format_range.ts @@ -1,5 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVerRange } from "./types.ts"; +import type { Range, SemVerRange } from "./types.ts"; import { comparatorFormat } from "./comparator_format.ts"; /** @@ -8,7 +8,9 @@ import { comparatorFormat } from "./comparator_format.ts"; * @param range The range to format * @returns A string representation of the range */ -export function formatRange(range: SemVerRange): string { - return range.ranges.map((c) => c.map((c) => comparatorFormat(c)).join(" ")) +export function formatRange(range: SemVerRange | Range): string { + return (Array.isArray(range) ? range : range.ranges).map((c) => + c.map((c) => comparatorFormat(c)).join(" ") + ) .join("||"); } diff --git a/semver/gtr.ts b/semver/gtr.ts index 0492ae180b28..6d32024e9218 100644 --- a/semver/gtr.ts +++ b/semver/gtr.ts @@ -1,12 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { rangeMax } from "./range_max.ts"; import { gt } from "./gt.ts"; /** Checks to see if the version is greater than all possible versions of the range. */ export function gtr( version: SemVer, - range: SemVerRange, + range: SemVerRange | Range, ): boolean { return gt(version, rangeMax(range)); } diff --git a/semver/is_range.ts b/semver/is_range.ts new file mode 100644 index 000000000000..201cf9849697 --- /dev/null +++ b/semver/is_range.ts @@ -0,0 +1,22 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import type { Range } from "./types.ts"; +import { isComparator } from "./is_comparator.ts"; + +/** + * Does a deep check on the object to determine if its a valid range. + * + * Objects with extra fields are still considered valid if they have at + * least the correct fields. + * + * Adds a type assertion if true. + * @param value The value to check if its a valid Range + * @returns True if its a valid Range otherwise false. + */ +export function isRange(value: unknown): value is Range { + if (value === null || value === undefined) return false; + if (!Array.isArray(value)) return false; + const range = value as Range; + return range.every((r) => + Array.isArray(r) && r.every((c) => isComparator(c)) + ); +} diff --git a/semver/is_range_test.ts b/semver/is_range_test.ts new file mode 100644 index 000000000000..0d8b5127cd20 --- /dev/null +++ b/semver/is_range_test.ts @@ -0,0 +1,20 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import { assert } from "../assert/mod.ts"; +import { ALL } from "./constants.ts"; +import { isRange } from "./is_range.ts"; + +Deno.test({ + name: "isRange()", + fn: async (t) => { + let i = 0; + const ranges: unknown[] = [[ + [ALL], + ]]; + for (const r of ranges) { + await t.step(`${(i++).toString().padStart(2, "0")}`, () => { + const actual = isRange(r); + assert(actual); + }); + } + }, +}); diff --git a/semver/is_semver_range.ts b/semver/is_semver_range.ts index e9068e28a03c..7e64762f49d0 100644 --- a/semver/is_semver_range.ts +++ b/semver/is_semver_range.ts @@ -11,6 +11,8 @@ import { isComparator } from "./is_comparator.ts"; * Adds a type assertion if true. * @param value The value to check if its a valid SemVerRange * @returns True if its a valid SemVerRange otherwise false. + * + * @deprecated (will be removed in 0.214.0) Use {@linkcode isRange} instead. */ export function isSemVerRange(value: unknown): value is SemVerRange { if (value === null || value === undefined) return false; @@ -18,7 +20,7 @@ export function isSemVerRange(value: unknown): value is SemVerRange { if (typeof value !== "object") return false; const { ranges } = value as SemVerRange; return ( - Array.isArray(ranges), - ranges.every((r) => Array.isArray(r) && r.every((c) => isComparator(c))) + Array.isArray(ranges) && + ranges.every((r) => Array.isArray(r) && r.every((c) => isComparator(c))) ); } diff --git a/semver/ltr.ts b/semver/ltr.ts index 19f4adb4217a..e86f5cd0e49a 100644 --- a/semver/ltr.ts +++ b/semver/ltr.ts @@ -1,12 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { lt } from "./lt.ts"; import { rangeMin } from "./range_min.ts"; /** Less than range comparison */ export function ltr( version: SemVer, - range: SemVerRange, + range: SemVerRange | Range, ): boolean { return lt(version, rangeMin(range)); } diff --git a/semver/max_satisfying.ts b/semver/max_satisfying.ts index b4feb31b6972..28f3407c7974 100644 --- a/semver/max_satisfying.ts +++ b/semver/max_satisfying.ts @@ -1,5 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { testRange } from "./test_range.ts"; import { gt } from "./gt.ts"; @@ -12,7 +12,7 @@ import { gt } from "./gt.ts"; */ export function maxSatisfying( versions: SemVer[], - range: SemVerRange, + range: SemVerRange | Range, ): SemVer | undefined { let max; for (const version of versions) { diff --git a/semver/min_satisfying.ts b/semver/min_satisfying.ts index f4e19ad04ae8..1152a04b26a6 100644 --- a/semver/min_satisfying.ts +++ b/semver/min_satisfying.ts @@ -1,5 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { testRange } from "./test_range.ts"; import { lt } from "./lt.ts"; @@ -12,7 +12,7 @@ import { lt } from "./lt.ts"; */ export function minSatisfying( versions: SemVer[], - range: SemVerRange, + range: SemVerRange | Range, ): SemVer | undefined { let min; for (const version of versions) { diff --git a/semver/outside.ts b/semver/outside.ts index a64b39c3b3c1..27d490376073 100644 --- a/semver/outside.ts +++ b/semver/outside.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { gt } from "./gt.ts"; import { lt } from "./lt.ts"; -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { rangeMax } from "./range_max.ts"; import { rangeMin } from "./range_min.ts"; @@ -16,7 +16,7 @@ import { rangeMin } from "./range_min.ts"; */ export function outside( version: SemVer, - range: SemVerRange, + range: SemVerRange | Range, hilo?: ">" | "<", ): boolean { switch (hilo) { diff --git a/semver/parse_comparator.ts b/semver/parse_comparator.ts index cbf5be297cc1..c0094abbe1be 100644 --- a/semver/parse_comparator.ts +++ b/semver/parse_comparator.ts @@ -47,5 +47,5 @@ export function parseComparator(comparator: string): Comparator { } : ANY; - return { operator, semver }; + return { operator, ...semver, semver }; } diff --git a/semver/parse_range.ts b/semver/parse_range.ts index 18ef42f8cea3..6aba9339674c 100644 --- a/semver/parse_range.ts +++ b/semver/parse_range.ts @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { ALL } from "./constants.ts"; -import type { SemVerRange } from "./types.ts"; +import type { Range, SemVerRange } from "./types.ts"; import { OPERATOR_XRANGE_REGEXP, XRANGE } from "./_shared.ts"; import { parseComparator } from "./parse_comparator.ts"; @@ -315,9 +315,10 @@ function parseRangeString(string: string) { * @param range The range set string * @returns A valid semantic range */ -export function parseRange(range: string): SemVerRange { +export function parseRange(range: string): SemVerRange & Range { const ranges = range .split(/\s*\|\|\s*/) .map((range) => parseHyphenRange(range).flatMap(parseRangeString)); - return { ranges }; + Object.defineProperty(ranges, "ranges", { value: ranges }); + return ranges as SemVerRange & Range; } diff --git a/semver/range_format.ts b/semver/range_format.ts index 73c7e2bff1ea..73db5b555ce3 100644 --- a/semver/range_format.ts +++ b/semver/range_format.ts @@ -1,7 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { formatRange } from "./format_range.ts"; -import { SemVerRange } from "./types.ts"; +import { Range, SemVerRange } from "./types.ts"; /** * Formats the range into a string @@ -11,6 +11,6 @@ import { SemVerRange } from "./types.ts"; * * @deprecated (will be removed after 0.213.0) Use {@linkcode formatRange} instead. */ -export function rangeFormat(range: SemVerRange): string { +export function rangeFormat(range: SemVerRange | Range): string { return formatRange(range); } diff --git a/semver/range_intersects.ts b/semver/range_intersects.ts index 6a7df20f588b..bc7813028c34 100644 --- a/semver/range_intersects.ts +++ b/semver/range_intersects.ts @@ -1,11 +1,13 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { comparatorIntersects } from "./comparator_intersects.ts"; -import type { Comparator, SemVerRange } from "./types.ts"; +import type { Comparator, Range, SemVerRange } from "./types.ts"; -function rangesSatisfiable(ranges: SemVerRange[]): boolean { +function rangesSatisfiable(ranges: (SemVerRange | Range)[]): boolean { return ranges.every((r) => { // For each OR at least one AND must be satisfiable - return r.ranges.some((comparators) => comparatorsSatisfiable(comparators)); + return (Array.isArray(r) ? r : r.ranges).some((comparators) => + comparatorsSatisfiable(comparators) + ); }); } @@ -28,12 +30,16 @@ function comparatorsSatisfiable(comparators: Comparator[]): boolean { * @param r1 range 1 * @returns returns true if any */ -export function rangeIntersects(r0: SemVerRange, r1: SemVerRange): boolean { - return rangesSatisfiable([r0, r1]) && r0.ranges.some((r00) => { - return r1.ranges.some((r11) => { - return r00.every((c0) => { - return r11.every((c1) => comparatorIntersects(c0, c1)); +export function rangeIntersects( + r0: SemVerRange | Range, + r1: SemVerRange | Range, +): boolean { + return rangesSatisfiable([r0, r1]) && + (Array.isArray(r0) ? r0 : r0.ranges).some((r00) => { + return (Array.isArray(r1) ? r1 : r1.ranges).some((r11) => { + return r00.every((c0) => { + return r11.every((c1) => comparatorIntersects(c0, c1)); + }); }); }); - }); } diff --git a/semver/range_max.ts b/semver/range_max.ts index aaa19a17ca01..a04f8efc3912 100644 --- a/semver/range_max.ts +++ b/semver/range_max.ts @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { INVALID } from "./constants.ts"; -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { testRange } from "./test_range.ts"; import { comparatorMax } from "./comparator_max.ts"; import { gt } from "./gt.ts"; @@ -10,11 +10,14 @@ import { gt } from "./gt.ts"; * @param range The range to calculate the max for * @returns A valid SemVer or INVALID */ -export function rangeMax(range: SemVerRange): SemVer { +export function rangeMax(range: SemVerRange | Range): SemVer { let max; - for (const comparators of range.ranges) { + for (const comparators of (Array.isArray(range) ? range : range.ranges)) { for (const comparator of comparators) { - const candidate = comparatorMax(comparator.semver, comparator.operator); + const candidate = comparatorMax( + comparator.semver ?? comparator, + comparator.operator, + ); if (!testRange(candidate, range)) continue; max = (max && gt(max, candidate)) ? max : candidate; } diff --git a/semver/range_min.ts b/semver/range_min.ts index bcd9d7ab0f5b..d78316c69436 100644 --- a/semver/range_min.ts +++ b/semver/range_min.ts @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. import { INVALID } from "./constants.ts"; -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { testRange } from "./test_range.ts"; import { comparatorMin } from "./comparator_min.ts"; import { lt } from "./lt.ts"; @@ -10,11 +10,14 @@ import { lt } from "./lt.ts"; * @param range The range to calculate the min for * @returns A valid SemVer or INVALID */ -export function rangeMin(range: SemVerRange): SemVer { +export function rangeMin(range: SemVerRange | Range): SemVer { let min; - for (const comparators of range.ranges) { + for (const comparators of (Array.isArray(range) ? range : range.ranges)) { for (const comparator of comparators) { - const candidate = comparatorMin(comparator.semver, comparator.operator); + const candidate = comparatorMin( + comparator.semver ?? comparator, + comparator.operator, + ); if (!testRange(candidate, range)) continue; min = (min && lt(min, candidate)) ? min : candidate; } diff --git a/semver/test_comparator.ts b/semver/test_comparator.ts index e6db7bb956ee..84620394843b 100644 --- a/semver/test_comparator.ts +++ b/semver/test_comparator.ts @@ -14,5 +14,5 @@ export function testComparator( version: SemVer, comparator: Comparator, ): boolean { - return cmp(version, comparator.operator, comparator.semver); + return cmp(version, comparator.operator, comparator.semver ?? comparator); } diff --git a/semver/test_range.ts b/semver/test_range.ts index 7d220e1a3929..9f32d2a72de5 100644 --- a/semver/test_range.ts +++ b/semver/test_range.ts @@ -1,5 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import type { SemVer, SemVerRange } from "./types.ts"; +import type { Range, SemVer, SemVerRange } from "./types.ts"; import { gte } from "./gte.ts"; import { lte } from "./lte.ts"; import { comparatorMin } from "./comparator_min.ts"; @@ -11,12 +11,15 @@ import { comparatorMax } from "./comparator_max.ts"; * @param range The range to check * @returns true if the version is in the range */ -export function testRange(version: SemVer, range: SemVerRange): boolean { - for (const r of range.ranges) { +export function testRange( + version: SemVer, + range: SemVerRange | Range, +): boolean { + for (const r of (Array.isArray(range) ? range : range.ranges)) { if ( r.every((c) => - gte(version, comparatorMin(c.semver, c.operator)) && - lte(version, comparatorMax(c.semver, c.operator)) + gte(version, comparatorMin(c.semver ?? c, c.operator)) && + lte(version, comparatorMax(c.semver ?? c, c.operator)) ) ) { return true; diff --git a/semver/try_parse_range.ts b/semver/try_parse_range.ts index 624f7bb7af30..d1c9e33629f9 100644 --- a/semver/try_parse_range.ts +++ b/semver/try_parse_range.ts @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { SemVerRange } from "./types.ts"; +import { Range, SemVerRange } from "./types.ts"; import { parseRange } from "./parse_range.ts"; /** @@ -10,7 +10,7 @@ import { parseRange } from "./parse_range.ts"; */ export function tryParseRange( range: string, -): SemVerRange | undefined { +): SemVerRange & Range | undefined { try { // Return '*' instead of '' so that truthiness works. // This will throw if it's invalid anyway diff --git a/semver/types.ts b/semver/types.ts index 1ad600c60a3a..6d407e4c5781 100644 --- a/semver/types.ts +++ b/semver/types.ts @@ -39,9 +39,12 @@ export type FormatStyle = * The shape of a valid semantic version comparator * @example >=0.0.0 */ -export interface Comparator { +export interface Comparator extends SemVer { operator: Operator; - semver: SemVer; + /** + * @deprecated (will be removed in 0.213.0) {@linkcode Comparator} extends {@linkcode SemVer}. Use `major`, `minor`, `patch`, `prerelease`, and `build` properties instead. + */ + semver?: SemVer; } /** @@ -55,15 +58,21 @@ export interface SemVer { build?: string[]; } -type SemVerRangeAnd = Comparator[]; -type SemVerRangeOr = SemVerRangeAnd[]; +/** + * A type representing a semantic version range. The ranges consist of + * a nested array, which represents a set of OR comparisons while the + * inner array represents AND comparisons. + */ +export type Range = Comparator[][]; /** * A type representing a semantic version range. The ranges consist of * a nested array, which represents a set of OR comparisons while the * inner array represents AND comparisons. + * + * @deprecated (will be removed in 0.214.0) Use {@linkcode Range} instead. */ export interface SemVerRange { // The outer array is OR while each inner array is AND - ranges: SemVerRangeOr; + ranges: Comparator[][]; } From 1b1d22ffde796d5e9043ab1fc26081323ab45da8 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Thu, 11 Jan 2024 10:09:47 +0100 Subject: [PATCH 2/8] Update semver/is_range.ts Co-authored-by: Asher Gomez --- semver/is_range.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/semver/is_range.ts b/semver/is_range.ts index 201cf9849697..a6fbfdbbb180 100644 --- a/semver/is_range.ts +++ b/semver/is_range.ts @@ -13,10 +13,6 @@ import { isComparator } from "./is_comparator.ts"; * @returns True if its a valid Range otherwise false. */ export function isRange(value: unknown): value is Range { - if (value === null || value === undefined) return false; - if (!Array.isArray(value)) return false; - const range = value as Range; - return range.every((r) => - Array.isArray(r) && r.every((c) => isComparator(c)) - ); + return Array.isArray(value) && + value.every((r) => Array.isArray(r) && r.every((c) => isComparator(c))); } From d719fd3cd72f38eec719c6a2b2990a8c6eeac836 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 11 Jan 2024 10:11:20 +0100 Subject: [PATCH 3/8] bump deprecation notice --- semver/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver/types.ts b/semver/types.ts index 6d407e4c5781..4552a3cefd51 100644 --- a/semver/types.ts +++ b/semver/types.ts @@ -42,7 +42,7 @@ export type FormatStyle = export interface Comparator extends SemVer { operator: Operator; /** - * @deprecated (will be removed in 0.213.0) {@linkcode Comparator} extends {@linkcode SemVer}. Use `major`, `minor`, `patch`, `prerelease`, and `build` properties instead. + * @deprecated (will be removed in 0.214.0) {@linkcode Comparator} extends {@linkcode SemVer}. Use `major`, `minor`, `patch`, `prerelease`, and `build` properties instead. */ semver?: SemVer; } From 3a0aa6d15a367e5971dd88090f889e999b2f3896 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 12 Jan 2024 23:33:18 +0100 Subject: [PATCH 4/8] add test cases --- semver/comparator_intersects_test.ts | 65 +++++++++++++++++++- semver/comparator_test.ts | 21 ++++++- semver/format_range_test.ts | 68 +++++++++++++++++++++ semver/range_intersects_test.ts | 89 +++++++++++++++++++++++++++- semver/range_min_test.ts | 78 +++++++++++++++++++++++- semver/test_comparator_test.ts | 13 +++- 6 files changed, 329 insertions(+), 5 deletions(-) diff --git a/semver/comparator_intersects_test.ts b/semver/comparator_intersects_test.ts index e8792d2caa16..ee936be1dad4 100644 --- a/semver/comparator_intersects_test.ts +++ b/semver/comparator_intersects_test.ts @@ -5,7 +5,7 @@ import { parseComparator } from "./parse_comparator.ts"; import { comparatorIntersects } from "./comparator_intersects.ts"; import { rangeIntersects } from "./range_intersects.ts"; -Deno.test("intersect", async (t) => { +Deno.test("comparatorIntersects() handles deprecated SemVerRange.ranges property", async (t) => { const versions: [string, string, boolean][] = [ // One is a Version ["1.3.0", ">=1.3.0", true], @@ -67,3 +67,66 @@ Deno.test("intersect", async (t) => { }); } }); + +Deno.test("comparatorIntersects()", async (t) => { + const versions: [string, string, boolean][] = [ + // One is a Version + ["1.3.0", ">=1.3.0", true], + ["1.3.0", ">1.3.0", false], + [">=1.3.0", "1.3.0", true], + [">1.3.0", "1.3.0", false], + + // Same direction increasing + [">1.3.0", ">1.2.0", true], + [">1.2.0", ">1.3.0", true], + [">=1.2.0", ">1.3.0", true], + [">1.2.0", ">=1.3.0", true], + + // Same direction decreasing + ["<1.3.0", "<1.2.0", true], + ["<1.2.0", "<1.3.0", true], + ["<=1.2.0", "<1.3.0", true], + ["<1.2.0", "<=1.3.0", true], + + // Different directions, same semver and inclusive operator + [">=1.3.0", "<=1.3.0", true], + [">=v1.3.0", "<=1.3.0", true], + [">=1.3.0", ">=1.3.0", true], + ["<=1.3.0", "<=1.3.0", true], + ["<=1.3.0", "<=v1.3.0", true], + [">1.3.0", "<=1.3.0", false], + [">=1.3.0", "<1.3.0", false], + + // Opposite matching directions + [">1.0.0", "<2.0.0", true], + [">=1.0.0", "<2.0.0", true], + [">=1.0.0", "<=2.0.0", true], + [">1.0.0", "<=2.0.0", true], + ["<=2.0.0", ">1.0.0", true], + ["<=1.0.0", ">=2.0.0", false], + ]; + for (const v of versions) { + const comparator1 = parseComparator(v[0]); + const comparator2 = parseComparator(v[1]); + const expect = v[2]; + await t.step({ + name: `${v[0]} ${expect ? "∩" : "∁"} ${v[1]}`, + fn: () => { + const actual1 = comparatorIntersects(comparator1, comparator2); + const actual2 = comparatorIntersects(comparator2, comparator1); + const actual3 = rangeIntersects( + [[comparator1]], + [[comparator2]], + ); + const actual4 = rangeIntersects( + [[comparator2]], + [[comparator1]], + ); + assertEquals(actual1, expect); + assertEquals(actual2, expect); + assertEquals(actual3, expect); + assertEquals(actual4, expect); + }, + }); + } +}); diff --git a/semver/comparator_test.ts b/semver/comparator_test.ts index d86ca0b0cd16..180c3b1df868 100644 --- a/semver/comparator_test.ts +++ b/semver/comparator_test.ts @@ -6,6 +6,7 @@ import { parse } from "./parse.ts"; import { testRange } from "./test_range.ts"; import { parseComparator } from "./parse_comparator.ts"; import { comparatorFormat } from "./comparator_format.ts"; +import { Comparator } from "https://deno.land/std@$STD_VERSION/semver/types.ts"; Deno.test({ name: "comparators", @@ -161,7 +162,7 @@ Deno.test({ }, }); -Deno.test("tostrings", function () { +Deno.test("comparatorFormat() handles semver inheritance", function () { assertEquals( comparatorFormat(parseComparator(">= v1.2.3")), ">=1.2.3", @@ -171,3 +172,21 @@ Deno.test("tostrings", function () { ">=1.2.3-pre.1+b.2", ); }); + +Deno.test("comparatorFormat() handles deprecated Comparator.semver property", function () { + const c1 = parseComparator(">= v1.2.3"); + assertEquals( + comparatorFormat( + { operator: c1.operator, semver: c1.semver } as Comparator, + ), + ">=1.2.3", + ); + const c2 = parseComparator(">= v1.2.3-pre.1+b.2"); + + assertEquals( + comparatorFormat( + { operator: c2.operator, semver: c2.semver } as Comparator, + ), + ">=1.2.3-pre.1+b.2", + ); +}); diff --git a/semver/format_range_test.ts b/semver/format_range_test.ts index f6757e8f9d3a..786810a3452a 100644 --- a/semver/format_range_test.ts +++ b/semver/format_range_test.ts @@ -71,3 +71,71 @@ Deno.test({ } }, }); + +Deno.test({ + name: "formatRange() handles deprecated SemVerRange.ranges property", + fn: async (t) => { + const versions: [string, string][] = [ + ["1.0.0 - 2.0.0", ">=1.0.0 <=2.0.0"], + ["1.0.0", "1.0.0"], + [">=*", "*"], + ["", "*"], + ["*", "*"], + [">=1.0.0", ">=1.0.0"], + [">1.0.0", ">1.0.0"], + ["<=2.0.0", "<=2.0.0"], + ["1", ">=1.0.0 <2.0.0"], + ["<=2.0.0", "<=2.0.0"], + ["<=2.0.0", "<=2.0.0"], + ["<2.0.0", "<2.0.0"], + ["<2.0.0", "<2.0.0"], + [">=0.1.97", ">=0.1.97"], + [">=0.1.97", ">=0.1.97"], + ["0.1.20 || 1.2.4", "0.1.20||1.2.4"], + [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"], + [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"], + [">=0.2.3 || <0.0.1", ">=0.2.3||<0.0.1"], + ["||", "*||*"], + ["2.x.x", ">=2.0.0 <3.0.0"], + ["1.2.x", ">=1.2.0 <1.3.0"], + ["1.2.x || 2.x", ">=1.2.0 <1.3.0||>=2.0.0 <3.0.0"], + ["1.2.x || 2.x", ">=1.2.0 <1.3.0||>=2.0.0 <3.0.0"], + ["x", "*"], + ["2.*.*", ">=2.0.0 <3.0.0"], + ["1.2.*", ">=1.2.0 <1.3.0"], + ["1.2.* || 2.*", ">=1.2.0 <1.3.0||>=2.0.0 <3.0.0"], + ["2", ">=2.0.0 <3.0.0"], + ["2.3", ">=2.3.0 <2.4.0"], + ["~2.4", ">=2.4.0 <2.5.0"], + ["~2.4", ">=2.4.0 <2.5.0"], + ["~>3.2.1", ">=3.2.1 <3.3.0"], + ["~1", ">=1.0.0 <2.0.0"], + ["~>1", ">=1.0.0 <2.0.0"], + ["~1.0", ">=1.0.0 <1.1.0"], + ["^0", ">=0.0.0 <1.0.0"], + ["^0.1", ">=0.1.0 <0.2.0"], + ["^1.0", ">=1.0.0 <2.0.0"], + ["^1.2", ">=1.2.0 <2.0.0"], + ["^0.0.1", ">=0.0.1 <0.0.2"], + ["^0.0.1-beta", ">=0.0.1-beta <0.0.2"], + ["^0.1.2", ">=0.1.2 <0.2.0"], + ["^1.2.3", ">=1.2.3 <2.0.0"], + ["^1.2.3-beta.4", ">=1.2.3-beta.4 <2.0.0"], + ["<1", "<1.0.0"], + [">=1", ">=1.0.0"], + ["<1.2", "<1.2.0"], + ["1", ">=1.0.0 <2.0.0"], + ]; + + for (const [r, expected] of versions) { + await t.step({ + name: `${r} -> ${expected}`, + fn: () => { + const range = parseRange(r); + const actual = formatRange({ ranges: range.ranges }); + assertEquals(actual, expected); + }, + }); + } + }, +}); diff --git a/semver/range_intersects_test.ts b/semver/range_intersects_test.ts index c19b9800b0a9..a8afb928844f 100644 --- a/semver/range_intersects_test.ts +++ b/semver/range_intersects_test.ts @@ -5,7 +5,7 @@ import { parseRange } from "./parse_range.ts"; import { rangeIntersects } from "./range_intersects.ts"; Deno.test({ - name: "rangesIntersect", + name: "rangesIntersects()", fn: async (t) => { const versions: [string, string, boolean][] = [ ["1.3.0 || <1.0.0 >2.0.0", "1.3.0 || <1.0.0 >2.0.0", true], @@ -86,3 +86,90 @@ Deno.test({ } }, }); + +Deno.test({ + name: "rangesIntersects() handles deprecated SemVerRange.ranges property", + fn: async (t) => { + const versions: [string, string, boolean][] = [ + ["1.3.0 || <1.0.0 >2.0.0", "1.3.0 || <1.0.0 >2.0.0", true], + [">0.0.0", "<1.0.0 >2.0.0", false], + ["<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.0.0", true], + ["<1.0.0 >=2.0.0", "2.1.0", false], + ["<1.0.0 >=2.0.0", ">1.4.0 <1.6.0 || 2.0.0", false], + ["1.5.x", "<1.5.0 || >=1.6.0", false], + ["<1.5.0 || >=1.6.0", "1.5.x", false], + [ + "<1.6.16 || >=1.7.0 <1.7.11 || >=1.8.0 <1.8.2", + ">=1.6.16 <1.7.0 || >=1.7.11 <1.8.0 || >=1.8.2", + false, + ], + [ + "<=1.6.16 || >=1.7.0 <1.7.11 || >=1.8.0 <1.8.2", + ">=1.6.16 <1.7.0 || >=1.7.11 <1.8.0 || >=1.8.2", + true, + ], + [">=1.0.0", "<=1.0.0", true], + [">1.0.0 <1.0.0", "<=0.0.0", false], + ["*", "0.0.1", true], + ["*", ">=1.0.0", true], + ["*", ">1.0.0", true], + ["*", "~1.0.0", true], + ["*", "<1.6.0", true], + ["*", "<=1.6.0", true], + ["1.*", "0.0.1", false], + ["1.*", "2.0.0", false], + ["1.*", "1.0.0", true], + ["1.*", "<2.0.0", true], + ["1.*", ">1.0.0", true], + ["1.*", "<=1.0.0", true], + ["1.*", "^1.0.0", true], + ["1.0.*", "0.0.1", false], + ["1.0.*", "<0.0.1", false], + ["1.0.*", ">0.0.1", true], + ["*", "1.3.0 || <1.0.0 >2.0.0", true], + ["1.3.0 || <1.0.0 >2.0.0", "*", true], + ["1.*", "1.3.0 || <1.0.0 >2.0.0", true], + ["x", "0.0.1", true], + ["x", ">=1.0.0", true], + ["x", ">1.0.0", true], + ["x", "~1.0.0", true], + ["x", "<1.6.0", true], + ["x", "<=1.6.0", true], + ["1.x", "0.0.1", false], + ["1.x", "2.0.0", false], + ["1.x", "1.0.0", true], + ["1.x", "<2.0.0", true], + ["1.x", ">1.0.0", true], + ["1.x", "<=1.0.0", true], + ["1.x", "^1.0.0", true], + ["1.0.x", "0.0.1", false], + ["1.0.x", "<0.0.1", false], + ["1.0.x", ">0.0.1", true], + ["x", "1.3.0 || <1.0.0 >2.0.0", true], + ["1.3.0 || <1.0.0 >2.0.0", "x", true], + ["1.x", "1.3.0 || <1.0.0 >2.0.0", true], + ["*", "*", true], + ["x", "", true], + ]; + + for (const [r1, r2, expected] of versions) { + await t.step({ + name: `${r1} ∩ ${r2}`, + fn: () => { + const range1 = parseRange(r1); + const range2 = parseRange(r2); + const actual1 = rangeIntersects({ ranges: range1.ranges }, { + ranges: range2.ranges, + }); + const actual2 = rangeIntersects({ ranges: range2.ranges }, { + ranges: range1.ranges, + }); + assertEquals(actual1, expected); + assertEquals(actual2, expected); + }, + }); + } + }, +}); diff --git a/semver/range_min_test.ts b/semver/range_min_test.ts index 2799ab27a1dc..65bf7e98b6f4 100644 --- a/semver/range_min_test.ts +++ b/semver/range_min_test.ts @@ -10,7 +10,7 @@ import { INVALID } from "./constants.ts"; import type { SemVer } from "./types.ts"; Deno.test({ - name: "rangeMin", + name: "rangeMin()", fn: async (t) => { const versions: [string, string | SemVer][] = [ // Stars @@ -84,3 +84,79 @@ Deno.test({ } }, }); + +Deno.test({ + name: "rangeMin() handles deprecated SemVerRange.ranges property", + fn: async (t) => { + const versions: [string, string | SemVer][] = [ + // Stars + ["*", "0.0.0"], + ["* || >=2", "0.0.0"], + [">=2 || *", "0.0.0"], + [">2 || *", "0.0.0"], + + // equal + ["1.0.0", "1.0.0"], + ["1.0", "1.0.0"], + ["1.0.x", "1.0.0"], + ["1.0.*", "1.0.0"], + ["1", "1.0.0"], + ["1.x.x", "1.0.0"], + ["1.x.x", "1.0.0"], + ["1.*.x", "1.0.0"], + ["1.x.*", "1.0.0"], + ["1.x", "1.0.0"], + ["1.*", "1.0.0"], + ["=1.0.0", "1.0.0"], + + // Tilde + ["~1.1.1", "1.1.1"], + ["~1.1.1-beta", "1.1.1-beta"], + ["~1.1.1 || >=2", "1.1.1"], + + // Caret + ["^1.1.1", "1.1.1"], + ["^1.1.1-beta", "1.1.1-beta"], + ["^1.1.1 || >=2", "1.1.1"], + + // '-' operator + ["1.1.1 - 1.8.0", "1.1.1"], + ["1.1 - 1.8.0", "1.1.0"], + + // Less / less or equal + ["<2", "0.0.0"], + ["<0.0.0-beta", INVALID], + ["<0.0.1-beta", "0.0.0"], + ["<2 || >4", "0.0.0"], + [">4 || <2", "0.0.0"], + ["<=2 || >=4", "0.0.0"], + [">=4 || <=2", "0.0.0"], + ["<0.0.0-beta >0.0.0-alpha", INVALID], + [">0.0.0-alpha <0.0.0-beta", INVALID], + + // Greater than or equal + [">=1.1.1 <2 || >=2.2.2 <2", "1.1.1"], + [">=2.2.2 <2 || >=1.1.1 <2", "1.1.1"], + + // Greater than but not equal + [">1.0.0", "1.0.1"], + [">1.0.0-0", "1.0.0-1"], + [">1.0.0-beta", "1.0.0-beta.0"], + [">2 || >1.0.0", "1.0.1"], + [">2 || >1.0.0-0", "1.0.0-1"], + [">2 || >1.0.0-beta", "1.0.0-beta.0"], + + // Impossible range + [">4 <3", INVALID], + ]; + + for (const [a, b] of versions) { + await t.step(a, () => { + const range = parseRange(a); + const version = typeof b === "string" ? parse(b) : b; + const min = rangeMin({ ranges: range.ranges }); + assert(eq(min, version), `${format(min)} != ${format(version)}`); + }); + } + }, +}); diff --git a/semver/test_comparator_test.ts b/semver/test_comparator_test.ts index 292dcd307eca..56c4078f6fe6 100644 --- a/semver/test_comparator_test.ts +++ b/semver/test_comparator_test.ts @@ -4,8 +4,19 @@ import { assert } from "../assert/mod.ts"; import { parse } from "./parse.ts"; import { parseComparator } from "./parse_comparator.ts"; import { testComparator } from "./test_comparator.ts"; +import { Comparator } from "./types.ts"; -Deno.test("test", function () { +Deno.test("testComparator()", () => { const c = parseComparator(">=1.2.3"); assert(testComparator(parse("1.2.4"), c)); }); + +Deno.test("testComparator() handles deprecated Comparator.semver property", () => { + const c = parseComparator(">=1.2.3"); + assert( + testComparator( + parse("1.2.4"), + { operator: c.operator, semver: c.semver } as Comparator, + ), + ); +}); From e1878f0fd7ec57fe08b1cd7046f8b721074b689d Mon Sep 17 00:00:00 2001 From: Tim Date: Sat, 13 Jan 2024 00:07:15 +0100 Subject: [PATCH 5/8] fmt --- semver/comparator_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver/comparator_test.ts b/semver/comparator_test.ts index 180c3b1df868..b7ba7a0d1147 100644 --- a/semver/comparator_test.ts +++ b/semver/comparator_test.ts @@ -6,7 +6,7 @@ import { parse } from "./parse.ts"; import { testRange } from "./test_range.ts"; import { parseComparator } from "./parse_comparator.ts"; import { comparatorFormat } from "./comparator_format.ts"; -import { Comparator } from "https://deno.land/std@$STD_VERSION/semver/types.ts"; +import { Comparator } from "./types.ts"; Deno.test({ name: "comparators", From f118a57838d82141e54e1bacd58ff5fd835fbd71 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Sun, 14 Jan 2024 12:32:31 +0100 Subject: [PATCH 6/8] Update types.ts Co-authored-by: Yoshiya Hinosawa --- semver/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver/types.ts b/semver/types.ts index 4552a3cefd51..6858d6985b64 100644 --- a/semver/types.ts +++ b/semver/types.ts @@ -70,7 +70,7 @@ export type Range = Comparator[][]; * a nested array, which represents a set of OR comparisons while the * inner array represents AND comparisons. * - * @deprecated (will be removed in 0.214.0) Use {@linkcode Range} instead. + * @deprecated (will be removed in 0.215.0) Use {@linkcode Range} instead. */ export interface SemVerRange { // The outer array is OR while each inner array is AND From 6e0331f70d2c2548a3283ef893d45180242c2337 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Sun, 14 Jan 2024 12:32:45 +0100 Subject: [PATCH 7/8] Update types.ts Co-authored-by: Yoshiya Hinosawa --- semver/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver/types.ts b/semver/types.ts index 6858d6985b64..030cb4bcfb36 100644 --- a/semver/types.ts +++ b/semver/types.ts @@ -42,7 +42,7 @@ export type FormatStyle = export interface Comparator extends SemVer { operator: Operator; /** - * @deprecated (will be removed in 0.214.0) {@linkcode Comparator} extends {@linkcode SemVer}. Use `major`, `minor`, `patch`, `prerelease`, and `build` properties instead. + * @deprecated (will be removed in 0.215.0) {@linkcode Comparator} extends {@linkcode SemVer}. Use `major`, `minor`, `patch`, `prerelease`, and `build` properties instead. */ semver?: SemVer; } From 3121795e5000ad9031a18bef7e275116b7bf2d27 Mon Sep 17 00:00:00 2001 From: Tim Reichen Date: Sun, 14 Jan 2024 12:32:50 +0100 Subject: [PATCH 8/8] Update is_semver_range.ts Co-authored-by: Yoshiya Hinosawa --- semver/is_semver_range.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver/is_semver_range.ts b/semver/is_semver_range.ts index 7e64762f49d0..f98c216dd93e 100644 --- a/semver/is_semver_range.ts +++ b/semver/is_semver_range.ts @@ -12,7 +12,7 @@ import { isComparator } from "./is_comparator.ts"; * @param value The value to check if its a valid SemVerRange * @returns True if its a valid SemVerRange otherwise false. * - * @deprecated (will be removed in 0.214.0) Use {@linkcode isRange} instead. + * @deprecated (will be removed in 0.215.0) Use {@linkcode isRange} instead. */ export function isSemVerRange(value: unknown): value is SemVerRange { if (value === null || value === undefined) return false;