All files / semver / range_intersects.ts

100.00% Branches 35/35
100.00% Lines 63/63
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
 
 
x25
x25
x25
 
 
x363
x363
x363
 
x363
x363
 
x363
 
x415
x1780
x445
x363
x397
x1676
x419
 
x615
 
x363
x363
x363
x363
x363
x363
x363
x363
x363
x363
x363
x363
x363
 
x363
x363
x363
x363
x363
x363
 
x196
x196
 
x532
x196
x196
 
x361
 
x361
x444
x444
x444
x456
x456
x444
x515
x685
x361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x25
x784
x196
x520
x699
x911
x911
 
x699
x520
x196
x196



































































































// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import { isWildcardComparator } from "./_shared.ts";
import { compare } from "./compare.ts";
import { satisfies } from "./satisfies.ts";
import type { Comparator, Range } from "./types.ts";

function comparatorIntersects(
  comparator1: Comparator,
  comparator2: Comparator,
): boolean {
  const op0 = comparator1.operator;
  const op1 = comparator2.operator;

  if (op0 === undefined) {
    // if comparator1 is empty comparator, then returns true
    if (isWildcardComparator(comparator1)) return true;
    return satisfies(comparator1, [[comparator2]]);
  }
  if (op1 === undefined) {
    if (isWildcardComparator(comparator2)) return true;
    return satisfies(comparator2, [[comparator1]]);
  }

  const cmp = compare(comparator1, comparator2);

  const sameDirectionIncreasing = (op0 === ">=" || op0 === ">") &&
    (op1 === ">=" || op1 === ">");
  const sameDirectionDecreasing = (op0 === "<=" || op0 === "<") &&
    (op1 === "<=" || op1 === "<");
  const sameSemVer = cmp === 0;
  const differentDirectionsInclusive = (op0 === ">=" || op0 === "<=") &&
    (op1 === ">=" || op1 === "<=");
  const oppositeDirectionsLessThan = cmp === -1 &&
    (op0 === ">=" || op0 === ">") &&
    (op1 === "<=" || op1 === "<");
  const oppositeDirectionsGreaterThan = cmp === 1 &&
    (op0 === "<=" || op0 === "<") &&
    (op1 === ">=" || op1 === ">");

  return sameDirectionIncreasing ||
    sameDirectionDecreasing ||
    (sameSemVer && differentDirectionsInclusive) ||
    oppositeDirectionsLessThan ||
    oppositeDirectionsGreaterThan;
}

function rangesSatisfiable(ranges: Range[]): boolean {
  return ranges.every((r) => {
    // For each OR at least one AND must be satisfiable
    return r.some((comparators) => comparatorsSatisfiable(comparators));
  });
}

function comparatorsSatisfiable(comparators: Comparator[]): boolean {
  // Comparators are satisfiable if they all intersect with each other
  for (let i = 0; i < comparators.length - 1; i++) {
    const comparator1 = comparators[i]!;
    for (const comparator2 of comparators.slice(i + 1)) {
      if (!comparatorIntersects(comparator1, comparator2)) {
        return false;
      }
    }
  }
  return true;
}

/**
 * The ranges intersect every range of AND comparators intersects with a least
 * one range of OR ranges.
 *
 * @example Usage
 * ```ts
 * import { parseRange, rangeIntersects } from "@std/semver";
 * import { assert } from "@std/assert";
 *
 * const range1 = parseRange(">=1.0.0 <2.0.0");
 * const range2 = parseRange(">=1.0.0 <1.2.3");
 * const range3 = parseRange(">=1.2.3 <2.0.0");
 *
 * assert(rangeIntersects(range1, range2));
 * assert(rangeIntersects(range1, range3));
 * assert(!rangeIntersects(range2, range3));
 * ```
 *
 * @param range1 range 0
 * @param range2 range 1
 * @returns returns true if the given ranges intersect, false otherwise
 */
export function rangeIntersects(range1: Range, range2: Range): boolean {
  return rangesSatisfiable([range1, range2]) &&
    range1.some((range10) => {
      return range2.some((r11) => {
        return range10.every((comparator1) => {
          return r11.every((comparator2) =>
            comparatorIntersects(comparator1, comparator2)
          );
        });
      });
    });
}