There are various methods for creating a type guard to ensure that an array is not empty. An example of this can be found here, which works well when using noUncheckedIndexedAccess:
type Indices<L extends number, T extends number[] = []> = T["length"] extends L
? T[number]
: Indices<L, [T["length"], ...T]>;
export type LengthAtLeast<T extends readonly any[], L extends number> = Pick<
Required<T>,
Indices<L>
>;
// Borrowed from: https://stackoverflow.com/a/69370003/521097
export function hasLengthAtLeast<T extends readonly any[], L extends number>(
arr: T,
len: L
): arr is T & LengthAtLeast<T, L> {
return arr.length >= len;
}
export function isNotEmpty<T extends readonly any[]>(arr: T): arr is T & LengthAtLeast<T, 1> {
return hasLengthAtLeast(arr, 1);
}
then:
let foo = [1, 2, 3];
if (isNotEmpty(foo))
foo[0].toString() // no error
else
foo[0].toString() // error
However, to check if the array is empty, you have to negate the boolean condition:
let foo = [1, 2, 3];
if (!isNotEmpty(foo))
foo[0].toString(); // now errors
else
foo[0].toString(); // no error
A potential issue arises with if (!isNotEmpty(foo))
as it involves a double negative making it hard to read.
So the question remains, how can we define an isEmpty
type guard so that we can simply do if (isEmpty(foo))
and achieve the same outcome as shown above? It seems like a simple problem, but my attempts have been unsuccessful so far.
I believe the main challenge lies in asserting the inverse of a type guard, indicating something IS NOT another thing.
EDIT: More examples have been requested.
Here is an example of what I aim to achieve:
function logFirstDataElement(data: number[]) {
// Do nothing if no data present
if (isEmpty(data)) return;
// This should not result in an error because data should be narrowed down to
// [T, ...T]
// ensuring it has at least one element
console.log(data[0].toString())
}
This can be accomplished by:
function logFirstDataElement(data: number[]) {
// Do nothing if no data present
if (!isNotEmpty(data)) return;
console.log(data[0].toString())
}
As mentioned earlier, I would prefer to avoid the "double negative" confusion of !isNotEmpty(data)
.