diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c50a6a7..e2dc544 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,8 +2,6 @@ name: Build on: pull_request: - types: - - opened push: branches: - main @@ -14,7 +12,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install dependencies run: npm install diff --git a/babel.config.js b/babel.config.js index 8fddd57..815eb05 100644 --- a/babel.config.js +++ b/babel.config.js @@ -32,7 +32,7 @@ module.exports = function (api) { regenerator: false, useESModules: false, // don't output es-modules by default corejs: false, - helpers: false, + helpers: true, }, ]; diff --git a/src/__tests__/jsonexpr/evaluator.test.js b/src/__tests__/jsonexpr/evaluator.test.js index bda52a1..53b1927 100644 --- a/src/__tests__/jsonexpr/evaluator.test.js +++ b/src/__tests__/jsonexpr/evaluator.test.js @@ -310,4 +310,161 @@ describe("Evaluator", () => { expect(evaluator.compare("100", "9")).toBe(-1); }); }); + + describe("versionCompare()", () => { + it("should return 0 for equal versions", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0", "1.0.0")).toBe(0); + expect(evaluator.versionCompare("0.0.0", "0.0.0")).toBe(0); + expect(evaluator.versionCompare("999.999.999", "999.999.999")).toBe(0); + }); + + it("should compare major versions", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("2.0.0", "1.0.0")).toBe(1); + expect(evaluator.versionCompare("1.0.0", "2.0.0")).toBe(-1); + }); + + it("should compare minor versions", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.2.0", "1.1.0")).toBe(1); + expect(evaluator.versionCompare("1.1.0", "1.2.0")).toBe(-1); + }); + + it("should compare patch versions", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.2", "1.0.1")).toBe(1); + expect(evaluator.versionCompare("1.0.1", "1.0.2")).toBe(-1); + }); + + it("should compare numerically not lexicographically", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.10.0", "1.9.0")).toBe(1); + expect(evaluator.versionCompare("1.9.0", "1.10.0")).toBe(-1); + expect(evaluator.versionCompare("10.0.0", "9.0.0")).toBe(1); + }); + + it("should treat missing parts as 0", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.2", "1.2.0")).toBe(0); + expect(evaluator.versionCompare("1", "1.0.0")).toBe(0); + expect(evaluator.versionCompare("1.2.0", "1.2")).toBe(0); + expect(evaluator.versionCompare("1.0.0", "1")).toBe(0); + }); + + it("should handle leading zeros in version parts", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.02.0", "1.2.0")).toBe(0); + expect(evaluator.versionCompare("1.002.030", "1.2.30")).toBe(0); + expect(evaluator.versionCompare("01.0.0", "1.0.0")).toBe(0); + expect(evaluator.versionCompare("1.02.0", "1.3.0")).toBe(-1); + }); + + it("should handle pre-release versions", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0", "1.0.0-alpha")).toBe(1); + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha")).toBe(0); + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-beta")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-beta", "1.0.0-alpha")).toBe(1); + expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha.2")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-alpha.2", "1.0.0-alpha.1")).toBe(1); + expect(evaluator.versionCompare("1.0.0-1", "1.0.0-2")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-2", "1.0.0-1")).toBe(1); + }); + + it("should compare numeric pre-release identifiers as less than string identifiers", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0-1", "1.0.0-alpha")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-1")).toBe(1); + }); + + it("should compare pre-release with fewer identifiers as less", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0-alpha", "1.0.0-alpha.1")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-alpha.1", "1.0.0-alpha")).toBe(1); + }); + + it("should strip v prefix", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("v1.0.0", "1.0.0")).toBe(0); + expect(evaluator.versionCompare("V1.0.0", "1.0.0")).toBe(0); + expect(evaluator.versionCompare("v1.0.0", "V1.0.0")).toBe(0); + }); + + it("should return null for null inputs", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare(null, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", null)).toBe(null); + expect(evaluator.versionCompare(null, null)).toBe(null); + }); + + it("should coerce non-string inputs via stringConvert", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare(1, "1.0.0")).toBe(0); + expect(evaluator.versionCompare("1.0.0", 1)).toBe(0); + expect(evaluator.versionCompare(true, "true")).toBe(0); + }); + + it("should return null for unconvertible inputs", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare({}, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", {})).toBe(null); + expect(evaluator.versionCompare([], "1.0.0")).toBe(null); + }); + + it("should ignore build metadata", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0+build2")).toBe(0); + expect(evaluator.versionCompare("1.0.0+build1", "1.0.0")).toBe(0); + }); + + it("should handle pre-release combined with build metadata", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-alpha+build2")).toBe(0); + expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0-beta")).toBe(-1); + expect(evaluator.versionCompare("1.0.0-alpha+build1", "1.0.0")).toBe(-1); + }); + + it("should return null for empty string inputs", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "")).toBe(null); + expect(evaluator.versionCompare("", "")).toBe(null); + }); + + it("should return null for undefined inputs", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare(undefined, "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", undefined)).toBe(null); + }); + + it("should return null for inputs that normalize to empty core", () => { + const evaluator = new Evaluator({}, {}); + + expect(evaluator.versionCompare("v", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("V", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("+build", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("v+build", "1.0.0")).toBe(null); + expect(evaluator.versionCompare("1.0.0", "v")).toBe(null); + }); + }); }); diff --git a/src/__tests__/jsonexpr/operators/evaluator.js b/src/__tests__/jsonexpr/operators/evaluator.js index 0fd3482..2c67b58 100644 --- a/src/__tests__/jsonexpr/operators/evaluator.js +++ b/src/__tests__/jsonexpr/operators/evaluator.js @@ -18,6 +18,21 @@ export function mockEvaluator() { return expr; }), + versionCompare: jest.fn((lhs, rhs) => { + const lhsStr = typeof lhs === "string" ? lhs : null; + const rhsStr = typeof rhs === "string" ? rhs : null; + if (lhsStr === null || rhsStr === null) return null; + const lParts = lhsStr.split(".").map(Number); + const rParts = rhsStr.split(".").map(Number); + const len = Math.max(lParts.length, rParts.length); + for (let i = 0; i < len; i++) { + const l = lParts[i] || 0; + const r = rParts[i] || 0; + if (l !== r) return l > r ? 1 : -1; + } + return 0; + }), + compare: jest.fn((lhs, rhs) => { switch (typeof lhs) { case "boolean": diff --git a/src/__tests__/jsonexpr/operators/semver_eq.test.js b/src/__tests__/jsonexpr/operators/semver_eq.test.js new file mode 100644 index 0000000..187747b --- /dev/null +++ b/src/__tests__/jsonexpr/operators/semver_eq.test.js @@ -0,0 +1,38 @@ +import { mockEvaluator } from "./evaluator"; +import { SemverEqualsOperator } from "../../../jsonexpr/operators/semver_eq"; + +describe("SemverEqualsOperator", () => { + const operator = new SemverEqualsOperator(); + + describe("evaluate", () => { + const evaluator = mockEvaluator(); + + afterEach(() => { + evaluator.evaluate.mockClear(); + evaluator.versionCompare.mockClear(); + }); + + it("should return true when versions are equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); + }); + + it("should return false when versions are not equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); + }); + + it("should return null when lhs is null", () => { + expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(1); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + + it("should return null when rhs is null", () => { + expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/jsonexpr/operators/semver_gt.test.js b/src/__tests__/jsonexpr/operators/semver_gt.test.js new file mode 100644 index 0000000..bb63d6f --- /dev/null +++ b/src/__tests__/jsonexpr/operators/semver_gt.test.js @@ -0,0 +1,42 @@ +import { mockEvaluator } from "./evaluator"; +import { SemverGreaterThanOperator } from "../../../jsonexpr/operators/semver_gt"; + +describe("SemverGreaterThanOperator", () => { + const operator = new SemverGreaterThanOperator(); + + describe("evaluate", () => { + const evaluator = mockEvaluator(); + + afterEach(() => { + evaluator.evaluate.mockClear(); + evaluator.versionCompare.mockClear(); + }); + + it("should return true when left version is greater", () => { + expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); + }); + + it("should return false when versions are equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); + }); + + it("should return false when left version is less", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); + }); + + it("should return null when lhs is null", () => { + expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(1); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + + it("should return null when rhs is null", () => { + expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/jsonexpr/operators/semver_gte.test.js b/src/__tests__/jsonexpr/operators/semver_gte.test.js new file mode 100644 index 0000000..38e6e84 --- /dev/null +++ b/src/__tests__/jsonexpr/operators/semver_gte.test.js @@ -0,0 +1,42 @@ +import { mockEvaluator } from "./evaluator"; +import { SemverGreaterThanOrEqualOperator } from "../../../jsonexpr/operators/semver_gte"; + +describe("SemverGreaterThanOrEqualOperator", () => { + const operator = new SemverGreaterThanOrEqualOperator(); + + describe("evaluate", () => { + const evaluator = mockEvaluator(); + + afterEach(() => { + evaluator.evaluate.mockClear(); + evaluator.versionCompare.mockClear(); + }); + + it("should return true when left version is greater", () => { + expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); + }); + + it("should return true when versions are equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); + }); + + it("should return false when left version is less", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); + }); + + it("should return null when lhs is null", () => { + expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(1); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + + it("should return null when rhs is null", () => { + expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/jsonexpr/operators/semver_lt.test.js b/src/__tests__/jsonexpr/operators/semver_lt.test.js new file mode 100644 index 0000000..410619f --- /dev/null +++ b/src/__tests__/jsonexpr/operators/semver_lt.test.js @@ -0,0 +1,42 @@ +import { mockEvaluator } from "./evaluator"; +import { SemverLessThanOperator } from "../../../jsonexpr/operators/semver_lt"; + +describe("SemverLessThanOperator", () => { + const operator = new SemverLessThanOperator(); + + describe("evaluate", () => { + const evaluator = mockEvaluator(); + + afterEach(() => { + evaluator.evaluate.mockClear(); + evaluator.versionCompare.mockClear(); + }); + + it("should return true when left version is less", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); + }); + + it("should return false when versions are equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); + }); + + it("should return false when left version is greater", () => { + expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); + }); + + it("should return null when lhs is null", () => { + expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(1); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + + it("should return null when rhs is null", () => { + expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/__tests__/jsonexpr/operators/semver_lte.test.js b/src/__tests__/jsonexpr/operators/semver_lte.test.js new file mode 100644 index 0000000..b4a870f --- /dev/null +++ b/src/__tests__/jsonexpr/operators/semver_lte.test.js @@ -0,0 +1,42 @@ +import { mockEvaluator } from "./evaluator"; +import { SemverLessThanOrEqualOperator } from "../../../jsonexpr/operators/semver_lte"; + +describe("SemverLessThanOrEqualOperator", () => { + const operator = new SemverLessThanOrEqualOperator(); + + describe("evaluate", () => { + const evaluator = mockEvaluator(); + + afterEach(() => { + evaluator.evaluate.mockClear(); + evaluator.versionCompare.mockClear(); + }); + + it("should return true when left version is less", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "2.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "2.0.0"); + }); + + it("should return true when versions are equal", () => { + expect(operator.evaluate(evaluator, ["1.0.0", "1.0.0"])).toBe(true); + expect(evaluator.versionCompare).toHaveBeenCalledWith("1.0.0", "1.0.0"); + }); + + it("should return false when left version is greater", () => { + expect(operator.evaluate(evaluator, ["2.0.0", "1.0.0"])).toBe(false); + expect(evaluator.versionCompare).toHaveBeenCalledWith("2.0.0", "1.0.0"); + }); + + it("should return null when lhs is null", () => { + expect(operator.evaluate(evaluator, [null, "1.0.0"])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(1); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + + it("should return null when rhs is null", () => { + expect(operator.evaluate(evaluator, ["1.0.0", null])).toBe(null); + expect(evaluator.evaluate).toHaveBeenCalledTimes(2); + expect(evaluator.versionCompare).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/jsonexpr/evaluator.ts b/src/jsonexpr/evaluator.ts index 17d87d0..5c52ff0 100644 --- a/src/jsonexpr/evaluator.ts +++ b/src/jsonexpr/evaluator.ts @@ -1,6 +1,57 @@ /* eslint-disable */ import { isEqualsDeep, isObject } from "../utils"; +function parseSemver(version: string) { + let v = version; + if (v.startsWith("v") || v.startsWith("V")) { + v = v.substring(1); + } + + const plusIndex = v.indexOf("+"); + if (plusIndex >= 0) { + v = v.substring(0, plusIndex); + } + + if (v === "") { + return null; + } + + const [core, ...preReleaseParts] = v.split("-"); + const preRelease = preReleaseParts.join("-"); + + if (core === "") { + return null; + } + + const parts = core.split("."); + + return { parts, preRelease }; +} + +const NUMERIC_IDENTIFIER = /^\d+$/; + +function stripLeadingZeros(s: string) { + const stripped = s.replace(/^0+/, ""); + return stripped === "" ? "0" : stripped; +} + +function compareIdentifiers(a: string, b: string) { + const aIsNum = NUMERIC_IDENTIFIER.test(a); + const bIsNum = NUMERIC_IDENTIFIER.test(b); + + if (aIsNum && bIsNum) { + const aNorm = stripLeadingZeros(a); + const bNorm = stripLeadingZeros(b); + if (aNorm.length !== bNorm.length) { + return aNorm.length > bNorm.length ? 1 : -1; + } + return aNorm === bNorm ? 0 : aNorm > bNorm ? 1 : -1; + } + if (aIsNum) return -1; + if (bIsNum) return 1; + return a === b ? 0 : a > b ? 1 : -1; +} + export class Evaluator { private readonly operators: any; private readonly vars: any; @@ -87,6 +138,44 @@ export class Evaluator { return target; } + versionCompare(lhs: TData, rhs: TData): number | null { + const lhsStr = this.stringConvert(lhs); + const rhsStr = this.stringConvert(rhs); + if (lhsStr === null || rhsStr === null || lhsStr === "" || rhsStr === "") { + return null; + } + + const l = parseSemver(lhsStr); + const r = parseSemver(rhsStr); + if (l === null || r === null) { + return null; + } + + const maxLen = Math.max(l.parts.length, r.parts.length); + for (let i = 0; i < maxLen; i++) { + const lPart = l.parts[i] ?? "0"; + const rPart = r.parts[i] ?? "0"; + const result = compareIdentifiers(lPart, rPart); + if (result !== 0) return result; + } + + if (!l.preRelease && !r.preRelease) return 0; + if (!l.preRelease) return 1; + if (!r.preRelease) return -1; + + const lPreParts = l.preRelease.split("."); + const rPreParts = r.preRelease.split("."); + const preLen = Math.max(lPreParts.length, rPreParts.length); + for (let i = 0; i < preLen; i++) { + if (i >= lPreParts.length) return -1; + if (i >= rPreParts.length) return 1; + const result = compareIdentifiers(lPreParts[i], rPreParts[i]); + if (result !== 0) return result; + } + + return 0; + } + compare(lhs: TData, rhs: TData) { if (lhs === null) { return rhs === null ? 0 : null; diff --git a/src/jsonexpr/jsonexpr.ts b/src/jsonexpr/jsonexpr.ts index 67e1ab3..84414c7 100644 --- a/src/jsonexpr/jsonexpr.ts +++ b/src/jsonexpr/jsonexpr.ts @@ -12,6 +12,11 @@ import { GreaterThanOperator } from "./operators/gt"; import { GreaterThanOrEqualOperator } from "./operators/gte"; import { LessThanOperator } from "./operators/lt"; import { LessThanOrEqualOperator } from "./operators/lte"; +import { SemverEqualsOperator } from "./operators/semver_eq"; +import { SemverGreaterThanOperator } from "./operators/semver_gt"; +import { SemverGreaterThanOrEqualOperator } from "./operators/semver_gte"; +import { SemverLessThanOperator } from "./operators/semver_lt"; +import { SemverLessThanOrEqualOperator } from "./operators/semver_lte"; const operators = { and: new AndCombinator(), @@ -27,6 +32,11 @@ const operators = { gte: new GreaterThanOrEqualOperator(), lt: new LessThanOperator(), lte: new LessThanOrEqualOperator(), + semver_eq: new SemverEqualsOperator(), + semver_gt: new SemverGreaterThanOperator(), + semver_gte: new SemverGreaterThanOrEqualOperator(), + semver_lt: new SemverLessThanOperator(), + semver_lte: new SemverLessThanOrEqualOperator(), }; export class JsonExpr { diff --git a/src/jsonexpr/operators/semver_eq.ts b/src/jsonexpr/operators/semver_eq.ts new file mode 100644 index 0000000..d35dfc2 --- /dev/null +++ b/src/jsonexpr/operators/semver_eq.ts @@ -0,0 +1,9 @@ +import { Evaluator } from "../evaluator"; +import { BinaryOperator } from "./binary"; + +export class SemverEqualsOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result === 0 : null; + } +} diff --git a/src/jsonexpr/operators/semver_gt.ts b/src/jsonexpr/operators/semver_gt.ts new file mode 100644 index 0000000..1b41166 --- /dev/null +++ b/src/jsonexpr/operators/semver_gt.ts @@ -0,0 +1,9 @@ +import { Evaluator } from "../evaluator"; +import { BinaryOperator } from "./binary"; + +export class SemverGreaterThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result > 0 : null; + } +} diff --git a/src/jsonexpr/operators/semver_gte.ts b/src/jsonexpr/operators/semver_gte.ts new file mode 100644 index 0000000..8525453 --- /dev/null +++ b/src/jsonexpr/operators/semver_gte.ts @@ -0,0 +1,9 @@ +import { Evaluator } from "../evaluator"; +import { BinaryOperator } from "./binary"; + +export class SemverGreaterThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result >= 0 : null; + } +} diff --git a/src/jsonexpr/operators/semver_lt.ts b/src/jsonexpr/operators/semver_lt.ts new file mode 100644 index 0000000..aa44efd --- /dev/null +++ b/src/jsonexpr/operators/semver_lt.ts @@ -0,0 +1,9 @@ +import { Evaluator } from "../evaluator"; +import { BinaryOperator } from "./binary"; + +export class SemverLessThanOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result < 0 : null; + } +} diff --git a/src/jsonexpr/operators/semver_lte.ts b/src/jsonexpr/operators/semver_lte.ts new file mode 100644 index 0000000..8d6261f --- /dev/null +++ b/src/jsonexpr/operators/semver_lte.ts @@ -0,0 +1,9 @@ +import { Evaluator } from "../evaluator"; +import { BinaryOperator } from "./binary"; + +export class SemverLessThanOrEqualOperator extends BinaryOperator { + binary(evaluator: Evaluator, lhs: unknown, rhs: unknown) { + const result = evaluator.versionCompare(lhs, rhs); + return result !== null ? result <= 0 : null; + } +}