danger-plugin-pr-hygiene
Version:
A Danger plugin for enforcing good PR hygiene.
2,255 lines (2,074 loc) • 60.5 kB
JavaScript
'use strict';
// Values marked with @internal are not part of the public API and may change
// without notice.
class CustomType {
withFields(fields) {
let properties = Object.keys(this).map((label) =>
label in fields ? fields[label] : this[label],
);
return new this.constructor(...properties);
}
}
class List {
static fromArray(array, tail) {
let t = tail || new Empty();
for (let i = array.length - 1; i >= 0; --i) {
t = new NonEmpty(array[i], t);
}
return t;
}
[Symbol.iterator]() {
return new ListIterator(this);
}
toArray() {
return [...this];
}
// @internal
atLeastLength(desired) {
for (let _ of this) {
if (desired <= 0) return true;
desired--;
}
return desired <= 0;
}
// @internal
hasLength(desired) {
for (let _ of this) {
if (desired <= 0) return false;
desired--;
}
return desired === 0;
}
// @internal
countLength() {
let length = 0;
for (let _ of this) length++;
return length;
}
}
// @internal
function prepend(element, tail) {
return new NonEmpty(element, tail);
}
function toList(elements, tail) {
return List.fromArray(elements, tail);
}
// @internal
class ListIterator {
#current;
constructor(current) {
this.#current = current;
}
next() {
if (this.#current instanceof Empty) {
return { done: true };
} else {
let { head, tail } = this.#current;
this.#current = tail;
return { value: head, done: false };
}
}
}
class Empty extends List {}
class NonEmpty extends List {
constructor(head, tail) {
super();
this.head = head;
this.tail = tail;
}
}
class Result extends CustomType {
// @internal
static isResult(data) {
return data instanceof Result;
}
}
class Ok extends Result {
constructor(value) {
super();
this[0] = value;
}
// @internal
isOk() {
return true;
}
}
class Error extends Result {
constructor(detail) {
super();
this[0] = detail;
}
// @internal
isOk() {
return false;
}
}
function isEqual(x, y) {
let values = [x, y];
while (values.length) {
let a = values.pop();
let b = values.pop();
if (a === b) continue;
if (!isObject(a) || !isObject(b)) return false;
let unequal =
!structurallyCompatibleObjects(a, b) ||
unequalDates(a, b) ||
unequalBuffers(a, b) ||
unequalArrays(a, b) ||
unequalMaps(a, b) ||
unequalSets(a, b) ||
unequalRegExps(a, b);
if (unequal) return false;
const proto = Object.getPrototypeOf(a);
if (proto !== null && typeof proto.equals === "function") {
try {
if (a.equals(b)) continue;
else return false;
} catch {}
}
let [keys, get] = getters(a);
for (let k of keys(a)) {
values.push(get(a, k), get(b, k));
}
}
return true;
}
function getters(object) {
if (object instanceof Map) {
return [(x) => x.keys(), (x, y) => x.get(y)];
} else {
let extra = object instanceof globalThis.Error ? ["message"] : [];
return [(x) => [...extra, ...Object.keys(x)], (x, y) => x[y]];
}
}
function unequalDates(a, b) {
return a instanceof Date && (a > b || a < b);
}
function unequalBuffers(a, b) {
return (
a.buffer instanceof ArrayBuffer &&
a.BYTES_PER_ELEMENT &&
!(a.byteLength === b.byteLength && a.every((n, i) => n === b[i]))
);
}
function unequalArrays(a, b) {
return Array.isArray(a) && a.length !== b.length;
}
function unequalMaps(a, b) {
return a instanceof Map && a.size !== b.size;
}
function unequalSets(a, b) {
return (
a instanceof Set && (a.size != b.size || [...a].some((e) => !b.has(e)))
);
}
function unequalRegExps(a, b) {
return a instanceof RegExp && (a.source !== b.source || a.flags !== b.flags);
}
function isObject(a) {
return typeof a === "object" && a !== null;
}
function structurallyCompatibleObjects(a, b) {
if (typeof a !== "object" && typeof b !== "object" && (!a || !b))
return false;
let nonstructural = [Promise, WeakSet, WeakMap, Function];
if (nonstructural.some((c) => a instanceof c)) return false;
return a.constructor === b.constructor;
}
// @internal
function remainderInt(a, b) {
if (b === 0) {
return 0;
} else {
return a % b;
}
}
// @internal
function divideInt(a, b) {
return Math.trunc(divideFloat(a, b));
}
// @internal
function divideFloat(a, b) {
if (b === 0) {
return 0;
} else {
return a / b;
}
}
// @internal
function makeError(variant, module, line, fn, message, extra) {
let error = new globalThis.Error(message);
error.gleam_error = variant;
error.module = module;
error.line = line;
error.function = fn;
// TODO: Remove this with Gleam v2.0.0
error.fn = fn;
for (let k in extra) error[k] = extra[k];
return error;
}
/// <reference types="./option.d.mts" />
class Some extends CustomType {
constructor(x0) {
super();
this[0] = x0;
}
}
class None extends CustomType {}
/// <reference types="./regex.d.mts" />
class Match extends CustomType {
constructor(content, submatches) {
super();
this.content = content;
this.submatches = submatches;
}
}
class CompileError extends CustomType {
constructor(error, byte_index) {
super();
this.error = error;
this.byte_index = byte_index;
}
}
class Options extends CustomType {
constructor(case_insensitive, multi_line) {
super();
this.case_insensitive = case_insensitive;
this.multi_line = multi_line;
}
}
function compile(pattern, options) {
return compile_regex(pattern, options);
}
function from_string$1(pattern) {
return compile(pattern, new Options(false, false));
}
function check(regex, content) {
return regex_check(regex, content);
}
function scan(regex, string) {
return regex_scan(regex, string);
}
/// <reference types="./result.d.mts" />
function is_ok(result) {
if (!result.isOk()) {
return false;
} else {
return true;
}
}
function map_error(result, fun) {
if (result.isOk()) {
let x = result[0];
return new Ok(x);
} else {
let error = result[0];
return new Error(fun(error));
}
}
/// <reference types="./iterator.d.mts" />
class Stop extends CustomType {}
class Continue extends CustomType {
constructor(x0, x1) {
super();
this[0] = x0;
this[1] = x1;
}
}
class Iterator extends CustomType {
constructor(continuation) {
super();
this.continuation = continuation;
}
}
class Next extends CustomType {
constructor(element, accumulator) {
super();
this.element = element;
this.accumulator = accumulator;
}
}
function stop() {
return new Stop();
}
function do_unfold(initial, f) {
return () => {
let $ = f(initial);
if ($ instanceof Next) {
let x = $.element;
let acc = $.accumulator;
return new Continue(x, do_unfold(acc, f));
} else {
return new Stop();
}
};
}
function unfold(initial, f) {
let _pipe = initial;
let _pipe$1 = do_unfold(_pipe, f);
return new Iterator(_pipe$1);
}
function repeatedly(f) {
return unfold(undefined, (_) => { return new Next(f(), undefined); });
}
function repeat$1(x) {
return repeatedly(() => { return x; });
}
function do_fold(loop$continuation, loop$f, loop$accumulator) {
while (true) {
let continuation = loop$continuation;
let f = loop$f;
let accumulator = loop$accumulator;
let $ = continuation();
if ($ instanceof Continue) {
let elem = $[0];
let next = $[1];
loop$continuation = next;
loop$f = f;
loop$accumulator = f(accumulator, elem);
} else {
return accumulator;
}
}
}
function fold$1(iterator, initial, f) {
let _pipe = iterator.continuation;
return do_fold(_pipe, f, initial);
}
function to_list(iterator) {
let _pipe = iterator;
let _pipe$1 = fold$1(
_pipe,
toList([]),
(acc, e) => { return prepend(e, acc); },
);
return reverse(_pipe$1);
}
function do_take$1(continuation, desired) {
return () => {
let $ = desired > 0;
if (!$) {
return new Stop();
} else {
let $1 = continuation();
if ($1 instanceof Stop) {
return new Stop();
} else {
let e = $1[0];
let next = $1[1];
return new Continue(e, do_take$1(next, desired - 1));
}
}
};
}
function take$1(iterator, desired) {
let _pipe = iterator.continuation;
let _pipe$1 = do_take$1(_pipe, desired);
return new Iterator(_pipe$1);
}
function do_append(first, second) {
let $ = first();
if ($ instanceof Continue) {
let e = $[0];
let first$1 = $[1];
return new Continue(e, () => { return do_append(first$1, second); });
} else {
return second();
}
}
function append(first, second) {
let _pipe = () => {
return do_append(first.continuation, second.continuation);
};
return new Iterator(_pipe);
}
function once(f) {
let _pipe = () => { return new Continue(f(), stop); };
return new Iterator(_pipe);
}
function single(elem) {
return once(() => { return elem; });
}
/// <reference types="./string_builder.d.mts" />
function from_strings(strings) {
return concat(strings);
}
function concat$2(builders) {
return concat(builders);
}
function from_string(string) {
return identity(string);
}
function to_string(builder) {
return identity(builder);
}
function split$2(iodata, pattern) {
return split(iodata, pattern);
}
/// <reference types="./string.d.mts" />
function length$1(string) {
return string_length(string);
}
function lowercase$1(string) {
return lowercase(string);
}
function ends_with$1(string, suffix) {
return ends_with(string, suffix);
}
function concat$1(strings) {
let _pipe = strings;
let _pipe$1 = from_strings(_pipe);
return to_string(_pipe$1);
}
function repeat(string, times) {
let _pipe = repeat$1(string);
let _pipe$1 = take$1(_pipe, times);
let _pipe$2 = to_list(_pipe$1);
return concat$1(_pipe$2);
}
function trim$1(string) {
return trim(string);
}
function do_slice(string, idx, len) {
let _pipe = string;
let _pipe$1 = graphemes(_pipe);
let _pipe$2 = drop(_pipe$1);
let _pipe$3 = take(_pipe$2, len);
return concat$1(_pipe$3);
}
function slice(string, idx, len) {
let $ = len < 0;
if ($) {
return "";
} else {
{
return do_slice(string, idx, len);
}
}
}
function split$1(x, substring) {
{
let _pipe = x;
let _pipe$1 = from_string(_pipe);
let _pipe$2 = split$2(_pipe$1, substring);
return map(_pipe$2, to_string);
}
}
function padding(size, pad_string) {
let pad_length = length$1(pad_string);
let num_pads = divideInt(size, pad_length);
let extra = remainderInt(size, pad_length);
let _pipe = repeat$1(pad_string);
let _pipe$1 = take$1(_pipe, num_pads);
return append(
_pipe$1,
single(slice(pad_string, 0, extra)),
);
}
function pad_left(string, desired_length, pad_string) {
let current_length = length$1(string);
let to_pad_length = desired_length - current_length;
let _pipe = padding(to_pad_length, pad_string);
let _pipe$1 = append(_pipe, single(string));
let _pipe$2 = to_list(_pipe$1);
return concat$1(_pipe$2);
}
/**
* This file uses jsdoc to annotate types.
* These types can be checked using the typescript compiler with "checkjs" option.
*/
const referenceMap = new WeakMap();
const tempDataView = new DataView(new ArrayBuffer(8));
let referenceUID = 0;
/**
* hash the object by reference using a weak map and incrementing uid
* @param {any} o
* @returns {number}
*/
function hashByReference(o) {
const known = referenceMap.get(o);
if (known !== undefined) {
return known;
}
const hash = referenceUID++;
if (referenceUID === 0x7fffffff) {
referenceUID = 0;
}
referenceMap.set(o, hash);
return hash;
}
/**
* merge two hashes in an order sensitive way
* @param {number} a
* @param {number} b
* @returns {number}
*/
function hashMerge(a, b) {
return (a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2))) | 0;
}
/**
* standard string hash popularised by java
* @param {string} s
* @returns {number}
*/
function hashString(s) {
let hash = 0;
const len = s.length;
for (let i = 0; i < len; i++) {
hash = (Math.imul(31, hash) + s.charCodeAt(i)) | 0;
}
return hash;
}
/**
* hash a number by converting to two integers and do some jumbling
* @param {number} n
* @returns {number}
*/
function hashNumber(n) {
tempDataView.setFloat64(0, n);
const i = tempDataView.getInt32(0);
const j = tempDataView.getInt32(4);
return Math.imul(0x45d9f3b, (i >> 16) ^ i) ^ j;
}
/**
* hash a BigInt by converting it to a string and hashing that
* @param {BigInt} n
* @returns {number}
*/
function hashBigInt(n) {
return hashString(n.toString());
}
/**
* hash any js object
* @param {any} o
* @returns {number}
*/
function hashObject(o) {
const proto = Object.getPrototypeOf(o);
if (proto !== null && typeof proto.hashCode === "function") {
try {
const code = o.hashCode(o);
if (typeof code === "number") {
return code;
}
} catch {}
}
if (o instanceof Promise || o instanceof WeakSet || o instanceof WeakMap) {
return hashByReference(o);
}
if (o instanceof Date) {
return hashNumber(o.getTime());
}
let h = 0;
if (o instanceof ArrayBuffer) {
o = new Uint8Array(o);
}
if (Array.isArray(o) || o instanceof Uint8Array) {
for (let i = 0; i < o.length; i++) {
h = (Math.imul(31, h) + getHash(o[i])) | 0;
}
} else if (o instanceof Set) {
o.forEach((v) => {
h = (h + getHash(v)) | 0;
});
} else if (o instanceof Map) {
o.forEach((v, k) => {
h = (h + hashMerge(getHash(v), getHash(k))) | 0;
});
} else {
const keys = Object.keys(o);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
const v = o[k];
h = (h + hashMerge(getHash(v), hashString(k))) | 0;
}
}
return h;
}
/**
* hash any js value
* @param {any} u
* @returns {number}
*/
function getHash(u) {
if (u === null) return 0x42108422;
if (u === undefined) return 0x42108423;
if (u === true) return 0x42108421;
if (u === false) return 0x42108420;
switch (typeof u) {
case "number":
return hashNumber(u);
case "string":
return hashString(u);
case "bigint":
return hashBigInt(u);
case "object":
return hashObject(u);
case "symbol":
return hashByReference(u);
case "function":
return hashByReference(u);
default:
return 0; // should be unreachable
}
}
/**
* @template K,V
* @typedef {ArrayNode<K,V> | IndexNode<K,V> | CollisionNode<K,V>} Node
*/
/**
* @template K,V
* @typedef {{ type: typeof ENTRY, k: K, v: V }} Entry
*/
/**
* @template K,V
* @typedef {{ type: typeof ARRAY_NODE, size: number, array: (undefined | Entry<K,V> | Node<K,V>)[] }} ArrayNode
*/
/**
* @template K,V
* @typedef {{ type: typeof INDEX_NODE, bitmap: number, array: (Entry<K,V> | Node<K,V>)[] }} IndexNode
*/
/**
* @template K,V
* @typedef {{ type: typeof COLLISION_NODE, hash: number, array: Entry<K, V>[] }} CollisionNode
*/
/**
* @typedef {{ val: boolean }} Flag
*/
const SHIFT = 5; // number of bits you need to shift by to get the next bucket
const BUCKET_SIZE = Math.pow(2, SHIFT);
const MASK = BUCKET_SIZE - 1; // used to zero out all bits not in the bucket
const MAX_INDEX_NODE = BUCKET_SIZE / 2; // when does index node grow into array node
const MIN_ARRAY_NODE = BUCKET_SIZE / 4; // when does array node shrink to index node
const ENTRY = 0;
const ARRAY_NODE = 1;
const INDEX_NODE = 2;
const COLLISION_NODE = 3;
/** @type {IndexNode<any,any>} */
const EMPTY = {
type: INDEX_NODE,
bitmap: 0,
array: [],
};
/**
* Mask the hash to get only the bucket corresponding to shift
* @param {number} hash
* @param {number} shift
* @returns {number}
*/
function mask(hash, shift) {
return (hash >>> shift) & MASK;
}
/**
* Set only the Nth bit where N is the masked hash
* @param {number} hash
* @param {number} shift
* @returns {number}
*/
function bitpos(hash, shift) {
return 1 << mask(hash, shift);
}
/**
* Count the number of 1 bits in a number
* @param {number} x
* @returns {number}
*/
function bitcount(x) {
x -= (x >> 1) & 0x55555555;
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0f0f0f0f;
x += x >> 8;
x += x >> 16;
return x & 0x7f;
}
/**
* Calculate the array index of an item in a bitmap index node
* @param {number} bitmap
* @param {number} bit
* @returns {number}
*/
function index(bitmap, bit) {
return bitcount(bitmap & (bit - 1));
}
/**
* Efficiently copy an array and set one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @param {T} val
* @returns {T[]}
*/
function cloneAndSet(arr, at, val) {
const len = arr.length;
const out = new Array(len);
for (let i = 0; i < len; ++i) {
out[i] = arr[i];
}
out[at] = val;
return out;
}
/**
* Efficiently copy an array and insert one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @param {T} val
* @returns {T[]}
*/
function spliceIn(arr, at, val) {
const len = arr.length;
const out = new Array(len + 1);
let i = 0;
let g = 0;
while (i < at) {
out[g++] = arr[i++];
}
out[g++] = val;
while (i < len) {
out[g++] = arr[i++];
}
return out;
}
/**
* Efficiently copy an array and remove one value at an index
* @template T
* @param {T[]} arr
* @param {number} at
* @returns {T[]}
*/
function spliceOut(arr, at) {
const len = arr.length;
const out = new Array(len - 1);
let i = 0;
let g = 0;
while (i < at) {
out[g++] = arr[i++];
}
++i;
while (i < len) {
out[g++] = arr[i++];
}
return out;
}
/**
* Create a new node containing two entries
* @template K,V
* @param {number} shift
* @param {K} key1
* @param {V} val1
* @param {number} key2hash
* @param {K} key2
* @param {V} val2
* @returns {Node<K,V>}
*/
function createNode(shift, key1, val1, key2hash, key2, val2) {
const key1hash = getHash(key1);
if (key1hash === key2hash) {
return {
type: COLLISION_NODE,
hash: key1hash,
array: [
{ type: ENTRY, k: key1, v: val1 },
{ type: ENTRY, k: key2, v: val2 },
],
};
}
const addedLeaf = { val: false };
return assoc(
assocIndex(EMPTY, shift, key1hash, key1, val1, addedLeaf),
shift,
key2hash,
key2,
val2,
addedLeaf
);
}
/**
* @template T,K,V
* @callback AssocFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @param {V} val
* @param {Flag} addedLeaf
* @returns {Node<K,V>}
*/
/**
* Associate a node with a new entry, creating a new node
* @template T,K,V
* @type {AssocFunction<Node<K,V>,K,V>}
*/
function assoc(root, shift, hash, key, val, addedLeaf) {
switch (root.type) {
case ARRAY_NODE:
return assocArray(root, shift, hash, key, val, addedLeaf);
case INDEX_NODE:
return assocIndex(root, shift, hash, key, val, addedLeaf);
case COLLISION_NODE:
return assocCollision(root, shift, hash, key, val, addedLeaf);
}
}
/**
* @template T,K,V
* @type {AssocFunction<ArrayNode<K,V>,K,V>}
*/
function assocArray(root, shift, hash, key, val, addedLeaf) {
const idx = mask(hash, shift);
const node = root.array[idx];
// if the corresponding index is empty set the index to a newly created node
if (node === undefined) {
addedLeaf.val = true;
return {
type: ARRAY_NODE,
size: root.size + 1,
array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }),
};
}
if (node.type === ENTRY) {
// if keys are equal replace the entry
if (isEqual(key, node.k)) {
if (val === node.v) {
return root;
}
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, {
type: ENTRY,
k: key,
v: val,
}),
};
}
// otherwise upgrade the entry to a node and insert
addedLeaf.val = true;
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(
root.array,
idx,
createNode(shift + SHIFT, node.k, node.v, hash, key, val)
),
};
}
// otherwise call assoc on the child node
const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);
// if the child node hasn't changed just return the old root
if (n === node) {
return root;
}
// otherwise set the index to the new node
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, n),
};
}
/**
* @template T,K,V
* @type {AssocFunction<IndexNode<K,V>,K,V>}
*/
function assocIndex(root, shift, hash, key, val, addedLeaf) {
const bit = bitpos(hash, shift);
const idx = index(root.bitmap, bit);
// if there is already a item at this hash index..
if ((root.bitmap & bit) !== 0) {
// if there is a node at the index (not an entry), call assoc on the child node
const node = root.array[idx];
if (node.type !== ENTRY) {
const n = assoc(node, shift + SHIFT, hash, key, val, addedLeaf);
if (n === node) {
return root;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, n),
};
}
// otherwise there is an entry at the index
// if the keys are equal replace the entry with the updated value
const nodeKey = node.k;
if (isEqual(key, nodeKey)) {
if (val === node.v) {
return root;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, {
type: ENTRY,
k: key,
v: val,
}),
};
}
// if the keys are not equal, replace the entry with a new child node
addedLeaf.val = true;
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(
root.array,
idx,
createNode(shift + SHIFT, nodeKey, node.v, hash, key, val)
),
};
} else {
// else there is currently no item at the hash index
const n = root.array.length;
// if the number of nodes is at the maximum, expand this node into an array node
if (n >= MAX_INDEX_NODE) {
// create a 32 length array for the new array node (one for each bit in the hash)
const nodes = new Array(32);
// create and insert a node for the new entry
const jdx = mask(hash, shift);
nodes[jdx] = assocIndex(EMPTY, shift + SHIFT, hash, key, val, addedLeaf);
let j = 0;
let bitmap = root.bitmap;
// place each item in the index node into the correct spot in the array node
// loop through all 32 bits / array positions
for (let i = 0; i < 32; i++) {
if ((bitmap & 1) !== 0) {
const node = root.array[j++];
nodes[i] = node;
}
// shift the bitmap to process the next bit
bitmap = bitmap >>> 1;
}
return {
type: ARRAY_NODE,
size: n + 1,
array: nodes,
};
} else {
// else there is still space in this index node
// simply insert a new entry at the hash index
const newArray = spliceIn(root.array, idx, {
type: ENTRY,
k: key,
v: val,
});
addedLeaf.val = true;
return {
type: INDEX_NODE,
bitmap: root.bitmap | bit,
array: newArray,
};
}
}
}
/**
* @template T,K,V
* @type {AssocFunction<CollisionNode<K,V>,K,V>}
*/
function assocCollision(root, shift, hash, key, val, addedLeaf) {
// if there is a hash collision
if (hash === root.hash) {
const idx = collisionIndexOf(root, key);
// if this key already exists replace the entry with the new value
if (idx !== -1) {
const entry = root.array[idx];
if (entry.v === val) {
return root;
}
return {
type: COLLISION_NODE,
hash: hash,
array: cloneAndSet(root.array, idx, { type: ENTRY, k: key, v: val }),
};
}
// otherwise insert the entry at the end of the array
const size = root.array.length;
addedLeaf.val = true;
return {
type: COLLISION_NODE,
hash: hash,
array: cloneAndSet(root.array, size, { type: ENTRY, k: key, v: val }),
};
}
// if there is no hash collision, upgrade to an index node
return assoc(
{
type: INDEX_NODE,
bitmap: bitpos(root.hash, shift),
array: [root],
},
shift,
hash,
key,
val,
addedLeaf
);
}
/**
* Find the index of a key in the collision node's array
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {number}
*/
function collisionIndexOf(root, key) {
const size = root.array.length;
for (let i = 0; i < size; i++) {
if (isEqual(key, root.array[i].k)) {
return i;
}
}
return -1;
}
/**
* @template T,K,V
* @callback FindFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @returns {undefined | Entry<K,V>}
*/
/**
* Return the found entry or undefined if not present in the root
* @template K,V
* @type {FindFunction<Node<K,V>,K,V>}
*/
function find(root, shift, hash, key) {
switch (root.type) {
case ARRAY_NODE:
return findArray(root, shift, hash, key);
case INDEX_NODE:
return findIndex(root, shift, hash, key);
case COLLISION_NODE:
return findCollision(root, key);
}
}
/**
* @template K,V
* @type {FindFunction<ArrayNode<K,V>,K,V>}
*/
function findArray(root, shift, hash, key) {
const idx = mask(hash, shift);
const node = root.array[idx];
if (node === undefined) {
return undefined;
}
if (node.type !== ENTRY) {
return find(node, shift + SHIFT, hash, key);
}
if (isEqual(key, node.k)) {
return node;
}
return undefined;
}
/**
* @template K,V
* @type {FindFunction<IndexNode<K,V>,K,V>}
*/
function findIndex(root, shift, hash, key) {
const bit = bitpos(hash, shift);
if ((root.bitmap & bit) === 0) {
return undefined;
}
const idx = index(root.bitmap, bit);
const node = root.array[idx];
if (node.type !== ENTRY) {
return find(node, shift + SHIFT, hash, key);
}
if (isEqual(key, node.k)) {
return node;
}
return undefined;
}
/**
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {undefined | Entry<K,V>}
*/
function findCollision(root, key) {
const idx = collisionIndexOf(root, key);
if (idx < 0) {
return undefined;
}
return root.array[idx];
}
/**
* @template T,K,V
* @callback WithoutFunction
* @param {T} root
* @param {number} shift
* @param {number} hash
* @param {K} key
* @returns {undefined | Node<K,V>}
*/
/**
* Remove an entry from the root, returning the updated root.
* Returns undefined if the node should be removed from the parent.
* @template K,V
* @type {WithoutFunction<Node<K,V>,K,V>}
* */
function without(root, shift, hash, key) {
switch (root.type) {
case ARRAY_NODE:
return withoutArray(root, shift, hash, key);
case INDEX_NODE:
return withoutIndex(root, shift, hash, key);
case COLLISION_NODE:
return withoutCollision(root, key);
}
}
/**
* @template K,V
* @type {WithoutFunction<ArrayNode<K,V>,K,V>}
*/
function withoutArray(root, shift, hash, key) {
const idx = mask(hash, shift);
const node = root.array[idx];
if (node === undefined) {
return root; // already empty
}
let n = undefined;
// if node is an entry and the keys are not equal there is nothing to remove
// if node is not an entry do a recursive call
if (node.type === ENTRY) {
if (!isEqual(node.k, key)) {
return root; // no changes
}
} else {
n = without(node, shift + SHIFT, hash, key);
if (n === node) {
return root; // no changes
}
}
// if the recursive call returned undefined the node should be removed
if (n === undefined) {
// if the number of child nodes is at the minimum, pack into an index node
if (root.size <= MIN_ARRAY_NODE) {
const arr = root.array;
const out = new Array(root.size - 1);
let i = 0;
let j = 0;
let bitmap = 0;
while (i < idx) {
const nv = arr[i];
if (nv !== undefined) {
out[j] = nv;
bitmap |= 1 << i;
++j;
}
++i;
}
++i; // skip copying the removed node
while (i < arr.length) {
const nv = arr[i];
if (nv !== undefined) {
out[j] = nv;
bitmap |= 1 << i;
++j;
}
++i;
}
return {
type: INDEX_NODE,
bitmap: bitmap,
array: out,
};
}
return {
type: ARRAY_NODE,
size: root.size - 1,
array: cloneAndSet(root.array, idx, n),
};
}
return {
type: ARRAY_NODE,
size: root.size,
array: cloneAndSet(root.array, idx, n),
};
}
/**
* @template K,V
* @type {WithoutFunction<IndexNode<K,V>,K,V>}
*/
function withoutIndex(root, shift, hash, key) {
const bit = bitpos(hash, shift);
if ((root.bitmap & bit) === 0) {
return root; // already empty
}
const idx = index(root.bitmap, bit);
const node = root.array[idx];
// if the item is not an entry
if (node.type !== ENTRY) {
const n = without(node, shift + SHIFT, hash, key);
if (n === node) {
return root; // no changes
}
// if not undefined, the child node still has items, so update it
if (n !== undefined) {
return {
type: INDEX_NODE,
bitmap: root.bitmap,
array: cloneAndSet(root.array, idx, n),
};
}
// otherwise the child node should be removed
// if it was the only child node, remove this node from the parent
if (root.bitmap === bit) {
return undefined;
}
// otherwise just remove the child node
return {
type: INDEX_NODE,
bitmap: root.bitmap ^ bit,
array: spliceOut(root.array, idx),
};
}
// otherwise the item is an entry, remove it if the key matches
if (isEqual(key, node.k)) {
if (root.bitmap === bit) {
return undefined;
}
return {
type: INDEX_NODE,
bitmap: root.bitmap ^ bit,
array: spliceOut(root.array, idx),
};
}
return root;
}
/**
* @template K,V
* @param {CollisionNode<K,V>} root
* @param {K} key
* @returns {undefined | Node<K,V>}
*/
function withoutCollision(root, key) {
const idx = collisionIndexOf(root, key);
// if the key not found, no changes
if (idx < 0) {
return root;
}
// otherwise the entry was found, remove it
// if it was the only entry in this node, remove the whole node
if (root.array.length === 1) {
return undefined;
}
// otherwise just remove the entry
return {
type: COLLISION_NODE,
hash: root.hash,
array: spliceOut(root.array, idx),
};
}
/**
* @template K,V
* @param {undefined | Node<K,V>} root
* @param {(value:V,key:K)=>void} fn
* @returns {void}
*/
function forEach(root, fn) {
if (root === undefined) {
return;
}
const items = root.array;
const size = items.length;
for (let i = 0; i < size; i++) {
const item = items[i];
if (item === undefined) {
continue;
}
if (item.type === ENTRY) {
fn(item.v, item.k);
continue;
}
forEach(item, fn);
}
}
/**
* Extra wrapper to keep track of Dict size and clean up the API
* @template K,V
*/
class Dict {
/**
* @template V
* @param {Record<string,V>} o
* @returns {Dict<string,V>}
*/
static fromObject(o) {
const keys = Object.keys(o);
/** @type Dict<string,V> */
let m = Dict.new();
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
m = m.set(k, o[k]);
}
return m;
}
/**
* @template K,V
* @param {Map<K,V>} o
* @returns {Dict<K,V>}
*/
static fromMap(o) {
/** @type Dict<K,V> */
let m = Dict.new();
o.forEach((v, k) => {
m = m.set(k, v);
});
return m;
}
static new() {
return new Dict(undefined, 0);
}
/**
* @param {undefined | Node<K,V>} root
* @param {number} size
*/
constructor(root, size) {
this.root = root;
this.size = size;
}
/**
* @template NotFound
* @param {K} key
* @param {NotFound} notFound
* @returns {NotFound | V}
*/
get(key, notFound) {
if (this.root === undefined) {
return notFound;
}
const found = find(this.root, 0, getHash(key), key);
if (found === undefined) {
return notFound;
}
return found.v;
}
/**
* @param {K} key
* @param {V} val
* @returns {Dict<K,V>}
*/
set(key, val) {
const addedLeaf = { val: false };
const root = this.root === undefined ? EMPTY : this.root;
const newRoot = assoc(root, 0, getHash(key), key, val, addedLeaf);
if (newRoot === this.root) {
return this;
}
return new Dict(newRoot, addedLeaf.val ? this.size + 1 : this.size);
}
/**
* @param {K} key
* @returns {Dict<K,V>}
*/
delete(key) {
if (this.root === undefined) {
return this;
}
const newRoot = without(this.root, 0, getHash(key), key);
if (newRoot === this.root) {
return this;
}
if (newRoot === undefined) {
return Dict.new();
}
return new Dict(newRoot, this.size - 1);
}
/**
* @param {K} key
* @returns {boolean}
*/
has(key) {
if (this.root === undefined) {
return false;
}
return find(this.root, 0, getHash(key), key) !== undefined;
}
/**
* @returns {[K,V][]}
*/
entries() {
if (this.root === undefined) {
return [];
}
/** @type [K,V][] */
const result = [];
this.forEach((v, k) => result.push([k, v]));
return result;
}
/**
*
* @param {(val:V,key:K)=>void} fn
*/
forEach(fn) {
forEach(this.root, fn);
}
hashCode() {
let h = 0;
this.forEach((v, k) => {
h = (h + hashMerge(getHash(v), getHash(k))) | 0;
});
return h;
}
/**
* @param {unknown} o
* @returns {boolean}
*/
equals(o) {
if (!(o instanceof Dict) || this.size !== o.size) {
return false;
}
let equal = true;
this.forEach((v, k) => {
equal = equal && isEqual(o.get(k, !v), v);
});
return equal;
}
}
const Nil = undefined;
const NOT_FOUND = {};
function identity(x) {
return x;
}
function string_length(string) {
if (string === "") {
return 0;
}
const iterator = graphemes_iterator(string);
if (iterator) {
let i = 0;
for (const _ of iterator) {
i++;
}
return i;
} else {
return string.match(/./gsu).length;
}
}
function graphemes(string) {
const iterator = graphemes_iterator(string);
if (iterator) {
return List.fromArray(Array.from(iterator).map((item) => item.segment));
} else {
return List.fromArray(string.match(/./gsu));
}
}
function graphemes_iterator(string) {
if (globalThis.Intl && Intl.Segmenter) {
return new Intl.Segmenter().segment(string)[Symbol.iterator]();
}
}
function lowercase(string) {
return string.toLowerCase();
}
function split(xs, pattern) {
return List.fromArray(xs.split(pattern));
}
function concat(xs) {
let result = "";
for (const x of xs) {
result = result + x;
}
return result;
}
function ends_with(haystack, needle) {
return haystack.endsWith(needle);
}
const unicode_whitespaces = [
"\u0020", // Space
"\u0009", // Horizontal tab
"\u000A", // Line feed
"\u000B", // Vertical tab
"\u000C", // Form feed
"\u000D", // Carriage return
"\u0085", // Next line
"\u2028", // Line separator
"\u2029", // Paragraph separator
].join("");
const left_trim_regex = new RegExp(`^([${unicode_whitespaces}]*)`, "g");
const right_trim_regex = new RegExp(`([${unicode_whitespaces}]*)$`, "g");
function trim(string) {
return trim_left(trim_right(string));
}
function trim_left(string) {
return string.replace(left_trim_regex, "");
}
function trim_right(string) {
return string.replace(right_trim_regex, "");
}
function regex_check(regex, string) {
regex.lastIndex = 0;
return regex.test(string);
}
function compile_regex(pattern, options) {
try {
let flags = "gu";
if (options.case_insensitive) flags += "i";
if (options.multi_line) flags += "m";
return new Ok(new RegExp(pattern, flags));
} catch (error) {
const number = (error.columnNumber || 0) | 0;
return new Error(new CompileError(error.message, number));
}
}
function regex_scan(regex, string) {
const matches = Array.from(string.matchAll(regex)).map((match) => {
const content = match[0];
const submatches = [];
for (let n = match.length - 1; n > 0; n--) {
if (match[n]) {
submatches[n - 1] = new Some(match[n]);
continue;
}
if (submatches.length > 0) {
submatches[n - 1] = new None();
}
}
return new Match(content, List.fromArray(submatches));
});
return List.fromArray(matches);
}
function new_map() {
return Dict.new();
}
function map_get(map, key) {
const value = map.get(key, NOT_FOUND);
if (value === NOT_FOUND) {
return new Error(Nil);
}
return new Ok(value);
}
function map_insert(key, value, map) {
return map.set(key, value);
}
function percent_encode$1(string) {
return encodeURIComponent(string).replace("%2B", "+");
}
/// <reference types="./dict.d.mts" />
function new$() {
return new_map();
}
function get(from, get) {
return map_get(from, get);
}
function insert(dict, key, value) {
return map_insert(key, value, dict);
}
/// <reference types="./list.d.mts" />
function count_length(loop$list, loop$count) {
while (true) {
let list = loop$list;
let count = loop$count;
if (list.atLeastLength(1)) {
let list$1 = list.tail;
loop$list = list$1;
loop$count = count + 1;
} else {
return count;
}
}
}
function length(list) {
return count_length(list, 0);
}
function do_reverse(loop$remaining, loop$accumulator) {
while (true) {
let remaining = loop$remaining;
let accumulator = loop$accumulator;
if (remaining.hasLength(0)) {
return accumulator;
} else {
let item = remaining.head;
let rest$1 = remaining.tail;
loop$remaining = rest$1;
loop$accumulator = prepend(item, accumulator);
}
}
}
function reverse(xs) {
return do_reverse(xs, toList([]));
}
function contains$1(loop$list, loop$elem) {
while (true) {
let list = loop$list;
let elem = loop$elem;
if (list.hasLength(0)) {
return false;
} else if (list.atLeastLength(1) && (isEqual(list.head, elem))) {
list.head;
return true;
} else {
let rest$1 = list.tail;
loop$list = rest$1;
loop$elem = elem;
}
}
}
function do_filter_map(loop$list, loop$fun, loop$acc) {
while (true) {
let list = loop$list;
let fun = loop$fun;
let acc = loop$acc;
if (list.hasLength(0)) {
return reverse(acc);
} else {
let x = list.head;
let xs = list.tail;
let new_acc = (() => {
let $ = fun(x);
if ($.isOk()) {
let x$1 = $[0];
return prepend(x$1, acc);
} else {
return acc;
}
})();
loop$list = xs;
loop$fun = fun;
loop$acc = new_acc;
}
}
}
function filter_map(list, fun) {
return do_filter_map(list, fun, toList([]));
}
function do_map(loop$list, loop$fun, loop$acc) {
while (true) {
let list = loop$list;
let fun = loop$fun;
let acc = loop$acc;
if (list.hasLength(0)) {
return reverse(acc);
} else {
let x = list.head;
let xs = list.tail;
loop$list = xs;
loop$fun = fun;
loop$acc = prepend(fun(x), acc);
}
}
}
function map(list, fun) {
return do_map(list, fun, toList([]));
}
function drop(loop$list, loop$n) {
while (true) {
let list = loop$list;
{
return list;
}
}
}
function do_take(loop$list, loop$n, loop$acc) {
while (true) {
let list = loop$list;
let n = loop$n;
let acc = loop$acc;
let $ = n <= 0;
if ($) {
return reverse(acc);
} else {
if (list.hasLength(0)) {
return reverse(acc);
} else {
let x = list.head;
let xs = list.tail;
loop$list = xs;
loop$n = n - 1;
loop$acc = prepend(x, acc);
}
}
}
}
function take(list, n) {
return do_take(list, n, toList([]));
}
function fold(loop$list, loop$initial, loop$fun) {
while (true) {
let list = loop$list;
let initial = loop$initial;
let fun = loop$fun;
if (list.hasLength(0)) {
return initial;
} else {
let x = list.head;
let rest$1 = list.tail;
loop$list = rest$1;
loop$initial = fun(initial, x);
loop$fun = fun;
}
}
}
function do_intersperse(loop$list, loop$separator, loop$acc) {
while (true) {
let list = loop$list;
let separator = loop$separator;
let acc = loop$acc;
if (list.hasLength(0)) {
return reverse(acc);
} else {
let x = list.head;
let rest$1 = list.tail;
loop$list = rest$1;
loop$separator = separator;
loop$acc = prepend(x, prepend(separator, acc));
}
}
}
function intersperse(list, elem) {
if (list.hasLength(0)) {
return list;
} else if (list.hasLength(1)) {
return list;
} else {
let x = list.head;
let rest$1 = list.tail;
return do_intersperse(rest$1, elem, toList([x]));
}
}
function each(loop$list, loop$f) {
while (true) {
let list = loop$list;
let f = loop$f;
if (list.hasLength(0)) {
return undefined;
} else {
let x = list.head;
let xs = list.tail;
f(x);
loop$list = xs;
loop$f = f;
}
}
}
function do_take_while(loop$list, loop$predicate, loop$acc) {
while (true) {
let list = loop$list;
let predicate = loop$predicate;
let acc = loop$acc;
if (list.hasLength(0)) {
return reverse(acc);
} else {
let first$1 = list.head;
let rest$1 = list.tail;
let $ = predicate(first$1);
if ($) {
loop$list = rest$1;
loop$predicate = predicate;
loop$acc = prepend(first$1, acc);
} else {
return reverse(acc);
}
}
}
}
function take_while(list, predicate) {
return do_take_while(list, predicate, toList([]));
}
/// <reference types="./uri.d.mts" />
function percent_encode(value) {
return percent_encode$1(value);
}
function query_pair(pair) {
return from_strings(
toList([percent_encode(pair[0]), "=", percent_encode(pair[1])]),
);
}
function query_to_string(query) {
let _pipe = query;
let _pipe$1 = map(_pipe, query_pair);
let _pipe$2 = intersperse(_pipe$1, from_string("&"));
let _pipe$3 = concat$2(_pipe$2);
return to_string(_pipe$3);
}
/// <reference types="./emit_level.d.mts" />
class Message extends CustomType {}
class Warn extends CustomType {}
class Fail extends CustomType {}
/// <reference types="./pr_title.d.mts" />
class PrTitle extends CustomType {
constructor(prefix, suffix) {
super();
this.prefix = prefix;
this.suffix = suffix;
}
}
function parse(pr_title, pattern) {
let matches = (() => {
let _pipe = pr_title;
return scan(pattern, _pipe);
})();
if (matches.hasLength(1)) {
let match = matches.head;
let $ = match.submatches;
if ($.hasLength(2) && $.head instanceof Some && $.tail.head instanceof Some) {
let prefix = $.head[0];
let suffix = $.tail.head[0];
return new PrTitle(
new Some(prefix),
(() => {
let _pipe = suffix;
return trim$1(_pipe);
})(),
);
} else {
return new PrTitle(new None(), pr_title);
}
} else {
return new PrTitle(new None(), pr_title);
}
}
/// <reference types="./rule.d.mts" />
class Violation extends CustomType {
constructor(span) {
super();
this.span = span;
}
}
/// <reference types="./no_trailing_punctuation.d.mts" />
class NoTrailingPunctuationConfig extends CustomType {
constructor(level, message) {
super();
this.level = level;
this.message = message;
}
}
function default_config$2() {
return new NoTrailingPunctuationConfig(
new Warn(),
"Do not end PR titles with punctuation.",
);
}
function no_trailing_punctuation(pr_title) {
let punctuation_marks = toList([".", "!", "?", ",", ":", ";"]);
let number_of_trailing_punctuation_marks = (() => {
let _pipe = pr_title;
let _pipe$1 = graphemes(_pipe);
let _pipe$2 = reverse(_pipe$1);
let _pipe$3 = take_while(
_pipe$2,
(_capture) => { return contains$1(punctuation_marks, _capture); },
);
return length(_pipe$3);
})();
let has_trailing_punctuation = number_of_trailing_punctuation_marks > 0;
if (has_trailing_punctuation) {
let end = length$1(pr_title);
let start = end - number_of_trailing_punctuation_marks;
return new Error(toList([new Violation([start, end])]));
} else {
return new Ok(undefined);
}
}
/// <reference types="./require_prefix.d.mts" />
class RequirePrefixConfig extends CustomType {
constructor(level, message) {
super();
this.level = level;
this.message = message;
}
}
function require_prefix(prefix_pattern, pr_title) {
let $ = parse(pr_title, prefix_pattern);
let prefix = $.prefix;
if (prefix instanceof None) {
return new Error(toList([new Violation([0, 0])]));
} else {
return new Ok(undefined);
}
}
/// <reference types="./set.d.mts" />
let Set$1 = class Set extends CustomType {
constructor(dict) {
super();
this.dict = dict;
}
};
function contains(set, member) {
let _pipe = set.dict;
let _pipe$1 = get(_pipe, member);
return is_ok(_pipe$1);
}
const token = undefined;
function from_list(members) {
let dict = fold(
members,
new$(),
(m, k) => { return insert(m, k, token); },
);
return new Set$1(dict);
}
/// <reference types="./verb_forms.d.mts" />
function third_person_singular_verbs() {
return from_list(
toList([
"adds",
"amends",
"applies",
"boosts",
"calculates",
"captures",
"changes",
"cleans",
"copies",
"deletes",
"deprecates",
"documents",
"duplicates",
"empowers",
"enhances",
"expands",
"explores",
"facilitates",
"fixes",
"forces",
"formalizes",
"implements",
"improves",
"initializes",
"integrates",
"makes",
"merges",
"migrates",
"modernizes",
"modifies",
"optimizes",
"patches",
"plugs",
"presents",
"refactors",
"removes",
"reshapes",
"resolves",
"reveals",
"reverts",
"revises",
"secures",
"simplifies",
"standardizes",
"stores",
"streamlines",
"supports",
"tests",
"transforms",
"tries",
"tweaks",
"undoes",
"unifies",
"updates",
"upgrades",
"writes",
]),
);
}
function is_third_person_singular(verb) {
let _pipe = verb;
let _pipe$1 = lowercase$1(_pipe);
return ((_capture) => {
return contains(third_person_singular_verbs(), _capture);
})(_pipe$1);
}
function is_past_tense(verb) {
return ends_with$1(verb, "ed");
}
function present_participle_exceptions() {
return from_list(toList(["bring"]));
}
function is_present_participle(verb) {
let $ = from_string$1("(\\w)*(ing)$");
if (!$.isOk()) {
throw makeError(
"let_assert",
"danger_plugin_pr_hygiene/grammar/verb_forms",
35,
"is_present_participle",
"Pattern match failed, no pattern matched the value.",
{ value: $ }
)
}
let pattern = $[0];
return check(pattern, verb) && !contains(
present_participle_exceptions(),
lowercase$1(verb),
);
}
function is_bare_infinitive(verb) {
return (!is_third_person_singular(verb) && !is_past_tense(verb)) && !is_present_participle(
verb,
);
}
/// <reference types="./use_imperative_mood.d.mts" />
class UseImperativeMoodConfig extends CustomType {
constructor(level, message) {
super();
this.level = level;
this.message = message;
}
}
function default_config$1() {
return new UseImperativeMoodConfig(
new Warn(),
"Write PR titles using the imperative mood.",
);
}
function use_imperative_mood(pr_title) {
let words = (() => {
let _pipe = pr_title;
return split$1(_pipe, " ");
})();
if (words.atLeastLength(1)) {
let word = words.head;
let $ = is_bare_infinitive(word);
if (!$) {
return new Error(toList([new Violation([0, length$1(word)])]));
} else {
return new Ok(undefined);
}
} else {
return new Ok(undefined);
}
}
/// <reference types="./use_sentence_case.d.mts" />
class UseSentenceCaseConfig extends CustomType {
constructor(level, message) {
super();
this.level = level;
this.message = message;
}
}
function default_config() {
return new UseSentenceCaseConfig(
new Warn(),
"Write PR titles using sentence case.",
);
}
function use_sentence_case(pr_title) {
let $ = from_string$1("^[a-z]");
if (!$.isOk()) {
throw makeError(
"let_assert",
"danger_plugin_pr_hygiene/rules/use_sentence_case",
17,
"use_sentence_case",
"Pattern match failed, no pattern matched the value.",
{ value: $ }
)
}
let pattern = $[0];
let $1 = check(pattern, pr_title);
if ($1) {
return new Error(toList([new Violation([0, 1])]));
} else {
return new Ok(undefined);
}
}
/// <reference types="./danger_plugin_pr_hygiene.d.mts" />
class PrHygieneContext extends CustomType {
constructor(message, warn, fail, markdown, pr_title) {
super();
this.message = message;
this.warn = warn;
this.fail = fail;
this.markdown = mar