From 3a446538b16ea7876c7ae361d572321ced9fa3fb Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 30 Jul 2024 12:31:20 +1000 Subject: [PATCH] fix(semver): throw on invalid input in `parseRange()` (#5567) * fix(semver): throw on invalid input in `parseRange()` * fix * fix * fix --- semver/_constants.ts | 8 -------- semver/is_range.ts | 4 ++-- semver/max_satisfying_test.ts | 6 ------ semver/min_satisfying_test.ts | 6 ------ semver/parse_range.ts | 18 +++++++++++------- semver/parse_range_test.ts | 24 +++++++++++------------- semver/satisfies_test.ts | 1 - 7 files changed, 24 insertions(+), 43 deletions(-) diff --git a/semver/_constants.ts b/semver/_constants.ts index a0bdac5d1..16d76f65d 100644 --- a/semver/_constants.ts +++ b/semver/_constants.ts @@ -57,14 +57,6 @@ export const ALL: Comparator = { ...ANY, }; -/** - * A comparator which will not span any semantic versions - */ -export const NONE: Comparator = { - operator: "<", - ...MIN, -}; - export const OPERATORS = [ undefined, "=", diff --git a/semver/is_range.ts b/semver/is_range.ts index c1e859cb3..34a0a37a5 100644 --- a/semver/is_range.ts +++ b/semver/is_range.ts @@ -2,7 +2,7 @@ // This module is browser compatible. import type { Comparator, Range } from "./types.ts"; import { OPERATORS } from "./_constants.ts"; -import { ALL, NONE } from "./_constants.ts"; +import { ALL } from "./_constants.ts"; import { isSemVer } from "./is_semver.ts"; function isComparator(value: unknown): value is Comparator { @@ -10,7 +10,7 @@ function isComparator(value: unknown): value is Comparator { value === null || value === undefined || Array.isArray(value) || typeof value !== "object" ) return false; - if (value === NONE || value === ALL) return true; + if (value === ALL) return true; const { operator } = value as Comparator; return ( (operator === undefined || diff --git a/semver/max_satisfying_test.ts b/semver/max_satisfying_test.ts index d684dee5b..dbcce249a 100644 --- a/semver/max_satisfying_test.ts +++ b/semver/max_satisfying_test.ts @@ -4,7 +4,6 @@ import { assertEquals } from "@std/assert"; import { parse } from "./parse.ts"; import { parseRange } from "./parse_range.ts"; import { maxSatisfying } from "./max_satisfying.ts"; -import { MAX, MIN } from "./_constants.ts"; Deno.test({ name: "maxSatisfying()", @@ -26,8 +25,3 @@ Deno.test({ } }, }); - -Deno.test("minSatisfying() handles bad ranges", function () { - const r = parseRange("some frogs and sneks-v2.5.6"); - assertEquals(maxSatisfying([MIN, MAX], r), undefined); -}); diff --git a/semver/min_satisfying_test.ts b/semver/min_satisfying_test.ts index cf068d19d..6262c92ce 100644 --- a/semver/min_satisfying_test.ts +++ b/semver/min_satisfying_test.ts @@ -4,7 +4,6 @@ import { assertEquals } from "@std/assert"; import { parse } from "./parse.ts"; import { parseRange } from "./parse_range.ts"; import { minSatisfying } from "./min_satisfying.ts"; -import { MAX, MIN } from "./_constants.ts"; Deno.test("minSatisfying()", async (t) => { const versions: [string[], string, string][] = [ @@ -23,8 +22,3 @@ Deno.test("minSatisfying()", async (t) => { }); } }); - -Deno.test("minSatisfying() handles bad ranges", function () { - const r = parseRange("some frogs and sneks-v2.5.6"); - assertEquals(minSatisfying([MIN, MAX], r), undefined); -}); diff --git a/semver/parse_range.ts b/semver/parse_range.ts index f60575c66..7be036177 100644 --- a/semver/parse_range.ts +++ b/semver/parse_range.ts @@ -9,7 +9,7 @@ import { parsePrerelease, XRANGE, } from "./_shared.ts"; -import { ALL, ANY, NONE } from "./_constants.ts"; +import { ALL, ANY } from "./_constants.ts"; import type { Comparator, Operator, Range } from "./types.ts"; type ComparatorRegExpGroup = { @@ -21,11 +21,11 @@ type ComparatorRegExpGroup = { buildmetadata: string; }; -function parseComparator(comparator: string): Comparator { +function parseComparator(comparator: string): Comparator | null { const match = comparator.match(COMPARATOR_REGEXP); const groups = match?.groups; - if (!groups) return NONE; + if (!groups) return null; const { operator, prerelease, buildmetadata } = groups as ComparatorRegExpGroup; @@ -349,7 +349,7 @@ function handleEqualOperator(groups: RangeRegExpGroups): Comparator[] { return [{ operator: undefined, major, minor, patch, prerelease, build }]; } -function parseOperatorRange(string: string): Comparator | Comparator[] { +function parseOperatorRange(string: string): Comparator | Comparator[] | null { const groups = string.match(OPERATOR_XRANGE_REGEXP) ?.groups as RangeRegExpGroups; if (!groups) return parseComparator(string); @@ -375,7 +375,7 @@ function parseOperatorRange(string: string): Comparator | Comparator[] { throw new Error(`'${groups.operator}' is not a valid operator.`); } } -function parseOperatorRanges(string: string): Comparator[] { +function parseOperatorRanges(string: string): (Comparator | null)[] { return string.split(/\s+/).flatMap(parseOperatorRange); } @@ -404,9 +404,13 @@ function parseOperatorRanges(string: string): Comparator[] { * @returns A valid SemVer range */ export function parseRange(range: string): Range { - return range + const result = range // remove spaces between operators and versions - .replaceAll(/(?<=<|>|=|~) +/g, "") + .replaceAll(/(?<=<|>|=|~|\^)(\s+)/g, "") .split(/\s*\|\|\s*/) .map((string) => parseHyphenRange(string) || parseOperatorRanges(string)); + if (result.some((r) => r.includes(null))) { + throw new TypeError(`Invalid range: ${range}`); + } + return result as Range; } diff --git a/semver/parse_range_test.ts b/semver/parse_range_test.ts index 79c2cab5c..6ce7ec3db 100644 --- a/semver/parse_range_test.ts +++ b/semver/parse_range_test.ts @@ -1,6 +1,6 @@ // Copyright Isaac Z. Schlueter and Contributors. All rights reserved. ISC license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -import { assertEquals } from "@std/assert"; +import { assertEquals, assertThrows } from "@std/assert"; import { parseRange } from "./parse_range.ts"; import type { Range } from "./types.ts"; @@ -351,18 +351,6 @@ Deno.test("parseRange() parse ranges of different kinds", () => { { operator: "<", major: 0, minor: 0, patch: 2 }, ], ]], - ["blerg", [ - [ - { - operator: "<", - major: 0, - minor: 0, - patch: 0, - prerelease: [], - build: [], - }, - ], - ]], ["^1.2.3", [ [ { operator: ">=", major: 1, minor: 2, patch: 3, prerelease: [] }, @@ -591,6 +579,12 @@ Deno.test("parseRange() parses ranges with caret", () => { { operator: "<", major: 2, minor: 0, patch: 0 }, ], ]], + ["^ 1.2.3", [ + [ + { operator: ">=", major: 1, minor: 2, patch: 3, prerelease: [] }, + { operator: "<", major: 2, minor: 0, patch: 0 }, + ], + ]], ["^0.2.3", [ [ { operator: ">=", major: 0, minor: 2, patch: 3, prerelease: [] }, @@ -664,3 +658,7 @@ Deno.test("parseRange() parses ranges with caret", () => { assertEquals(range, expected); } }); + +Deno.test("parseRange() throws on invalid range", () => { + assertThrows(() => parseRange("blerg"), TypeError, "Invalid range: blerg"); +}); diff --git a/semver/satisfies_test.ts b/semver/satisfies_test.ts index ed276d0a1..234ee537a 100644 --- a/semver/satisfies_test.ts +++ b/semver/satisfies_test.ts @@ -269,7 +269,6 @@ Deno.test({ ["<=1.2.3", "1.2.3-beta"], // invalid ranges never satisfied! - ["blerg", "1.2.3"], ["^1.2.3", "2.0.0-pre"], ["1.0.0 - 2.0.0", "0.0.0"],