refactor(semver): use capturing groups in parseComparator() (#4026)

This commit is contained in:
Tim Reichen 2023-12-30 10:43:56 +01:00 committed by GitHub
parent 9a0a21d604
commit 27c3c0257d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 42 deletions

View File

@ -119,7 +119,7 @@ const FULL_PLAIN = `v?${MAIN_VERSION}${PRERELEASE}?${BUILD}?`;
export const FULL_REGEXP = new RegExp(`^${FULL_PLAIN}$`);
const COMPARATOR = "((?:<|>)?=?)";
const COMPARATOR = "(?:<|>)?=?";
// Something like "2.*" or "1.2.x".
// Note that "x.x" is a valid xRange identifier, meaning "any version"
@ -130,7 +130,7 @@ export const XRANGE_PLAIN =
`[v=\\s]*(?<major>${XRANGE_IDENTIFIER})(?:\\.(?<minor>${XRANGE_IDENTIFIER})(?:\\.(?<patch>${XRANGE_IDENTIFIER})(?:${PRERELEASE})?${BUILD}?)?)?`;
export const XRANGE_REGEXP = new RegExp(
`^${COMPARATOR}\\s*${XRANGE_PLAIN}$`,
`^(?<operator>${COMPARATOR})\\s*${XRANGE_PLAIN}$`,
);
// Tilde ranges.
@ -143,7 +143,7 @@ export const CARET_REGEXP = new RegExp(`^(?<operator>\\^)${XRANGE_PLAIN}$`);
// A simple gt/lt/eq thing, or just "" to indicate "any version"
export const COMPARATOR_REGEXP = new RegExp(
`^${COMPARATOR}\\s*(${FULL_PLAIN})$|^$`,
`^(?<operator>${COMPARATOR})\\s*(${FULL_PLAIN})$|^$`,
);
// Star ranges basically just allow anything at all.
@ -209,3 +209,30 @@ export const operators = [
export function isOperator(value: unknown): value is Operator {
return operators.includes(value as Operator);
}
const NUMERIC_IDENTIFIER_REGEXP = new RegExp(`^(${NUMERIC_IDENTIFIER})$`);
export function parsePrerelease(prerelease: string) {
return prerelease
.split(".")
.filter((id) => id)
.map((id: string) => {
const num = parseInt(id);
if (id.match(NUMERIC_IDENTIFIER_REGEXP) && isValidNumber(num)) {
return num;
} else {
return id;
}
});
}
export function parseBuild(buildmetadata: string) {
return buildmetadata.split(".").filter((m) => m) ?? [];
}
export function parseNumber(input: string, errorMessage: string) {
const number = Number(input);
if (number > Number.MAX_SAFE_INTEGER || number < 0) {
throw new TypeError(errorMessage);
}
return number;
}

View File

@ -1,6 +1,6 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { SemVer } from "./types.ts";
import { isValidNumber } from "./_shared.ts";
import { parseBuild, parseNumber, parsePrerelease } from "./_shared.ts";
import { isSemVer } from "./is_semver.ts";
import { FULL_REGEXP, MAX_LENGTH } from "./_shared.ts";
@ -40,38 +40,15 @@ export function parse(version: string | SemVer): SemVer {
const groups = version.match(FULL_REGEXP)?.groups;
if (!groups) throw new TypeError(`Invalid Version: ${version}`);
// these are actually numbers
const major = parseInt(groups.major);
const minor = parseInt(groups.minor);
const patch = parseInt(groups.patch);
const major = parseNumber(groups.major, "Invalid major version");
const minor = parseNumber(groups.minor, "Invalid minor version");
const patch = parseNumber(groups.patch, "Invalid patch version");
if (major > Number.MAX_SAFE_INTEGER || major < 0) {
throw new TypeError("Invalid major version");
}
const prerelease = groups.prerelease
? parsePrerelease(groups.prerelease)
: [];
const build = groups.buildmetadata ? parseBuild(groups.buildmetadata) : [];
if (minor > Number.MAX_SAFE_INTEGER || minor < 0) {
throw new TypeError("Invalid minor version");
}
if (patch > Number.MAX_SAFE_INTEGER || patch < 0) {
throw new TypeError("Invalid patch version");
}
// number-ify any prerelease numeric ids
const numericIdentifier = new RegExp(`^(0|[1-9]\\d*)$`);
const prerelease = (groups.prerelease ?? "")
.split(".")
.filter((id) => id)
.map((id: string) => {
const num = parseInt(id);
if (id.match(numericIdentifier) && isValidNumber(num)) {
return num;
} else {
return id;
}
});
const build = groups.buildmetadata?.split(".")?.filter((m) => m) ?? [];
return {
major,
minor,

View File

@ -1,26 +1,54 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { parse } from "./parse.ts";
import type { Operator, SemVerComparator } from "./types.ts";
import { COMPARATOR_REGEXP } from "./_shared.ts";
import {
COMPARATOR_REGEXP,
parseBuild,
parseNumber,
parsePrerelease,
} from "./_shared.ts";
import { comparatorMin } from "./comparator_min.ts";
import { comparatorMax } from "./comparator_max.ts";
import { ANY, NONE } from "./constants.ts";
type REGEXP_GROUPS = {
operator: Operator;
major: string;
minor: string;
patch: string;
prerelease: string;
buildmetadata: string;
};
/**
* Parses a comparator string into a valid SemVerComparator.
* @param comparator
* @returns A valid SemVerComparator
*/
export function parseComparator(comparator: string): SemVerComparator {
const m = comparator.match(COMPARATOR_REGEXP);
const match = comparator.match(COMPARATOR_REGEXP);
const groups = match?.groups;
if (!m) {
return NONE;
}
if (!groups) return NONE;
const {
operator = "",
prerelease,
buildmetadata,
} = groups as REGEXP_GROUPS;
const semver = groups.major
? {
major: parseNumber(groups.major, "Invalid major version"),
minor: parseNumber(groups.minor, "Invalid minor version"),
patch: parseNumber(groups.patch, "Invalid patch version"),
prerelease: prerelease ? parsePrerelease(prerelease) : [],
build: buildmetadata ? parseBuild(buildmetadata) : [],
}
: ANY;
const operator = (m[1] ?? "") as Operator;
const semver = m[2] ? parse(m[2]) : ANY;
const min = comparatorMin(semver, operator);
const max = comparatorMax(semver, operator);
return { operator, semver, min, max };
}