Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(isNotEmpty) add types for isNotEmpty function, and update isEmpty #101

Merged
merged 16 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
"dox": "^1.0.0",
"eslint": "^8.50.0",
"eslint-plugin-import": "^2.28.1",
"ramda": "^0.29.1",
"ramda": "^0.30.0",
"rimraf": "^5.0.5",
"tsd": "^0.29.0",
"tsd": "^0.31.0",
"typescript": "^5.2.2",
"xyz": "^4.0.0"
}
Expand Down
61 changes: 49 additions & 12 deletions test/head.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,62 @@
import { expectType } from 'tsd';
import { head } from '../es';
import { isNotEmpty } from '../types/isNotEmpty';

// strings always return `string | undefined`, can't determine "emptiness" like you can with arrays
expectType<string | undefined>(head(''));
// string always return string
expectType<string>(head('abc'));
// emptyString still returns type string. this is due to ramda's implementation `''.chartAt(0) => ''`
expectType<string>(head(''));
expectType<string | undefined>(head('abc'));

// array literals will read the type of the first entry
expectType<string>(head(['fi', 1, 'fum']));
// but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
expectType<string | number | undefined>(head(['fi', 1, 'fum'] as Array<string | number>));
// empty array literals return never
expectType<never>(head([]));
// but if it is typed, it will be `T | undefined`
// empty array literals return undefined
expectType<undefined>(head([]));

// typed empty array will be `T | undefined`
expectType<number | undefined>(head([] as number[]));
// const tuples return the literal type of the first entry
// as will a typed populated array
expectType<number | undefined>(head([1, 2, 3] as number[]));

// const tuples return the literal type of the last entry
expectType<10>(head([10] as const));
expectType<10>(head([10, 'ten'] as const));
expectType<'10'>(head(['10', 10] as const));
// typed tuples return the underlying type
expectType<number>(head([10, 'ten'] as [number, string]));
expectType<string>(head(['10', 10] as [string, number]));
// typed tuples return the type of the last element
expectType<boolean>(head([true] as [boolean]));
expectType<number>(head([10, 'ten', true] as [number, string, boolean]));
expectType<string>(head(['10', 10, false] as [string, number, boolean]));
// typed empty tuple returns undefined, this is expected because there is no `T` here
expectType<undefined>(head([] as []));

// typed arrays return `T | undefined`
expectType<number | string | undefined>(head([10, 'ten'] as Array<number | string>));
expectType<string | number | undefined>(head(['10', 10] as Array<string | number>));
expectType<number | string | undefined>(head([10, 'ten'] as ReadonlyArray<number | string>));
expectType<string | number | undefined>(head(['10', 10] as ReadonlyArray<string | number>));

// cross function testing with isNotEmpty
// test the type narrowing
const readonlyArr: readonly number[] = [];
if (isNotEmpty(readonlyArr)) {
expectType<number>(head(readonlyArr));
}

const readonlyArr2: readonly number[] = [];
if (!isNotEmpty(readonlyArr2)) {
// no-op
} else {
expectType<number>(head(readonlyArr2));
}


const arr: number[] = [];
if (isNotEmpty(arr)) {
expectType<number>(head(arr));
}

const arr2: number[] = [];
if (!isNotEmpty(arr2)) {
// no-op
} else {
expectType<number>(head(arr2));
}
36 changes: 36 additions & 0 deletions test/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { expectType } from 'tsd';

// TODO: check this import to '../es' once this function actually exists in ramda
import { isNotEmpty } from '../types/isNotEmpty';
import { init } from '../es';

// string always return string
expectType<string>(init('abc'));
// emptyString still returns type string. this is due to `''.chartAt(0) => ''`
expectType<string>(init(''));

// array literals will read the first type correctly
expectType<[string, number]>(init(['fi', 1, 'fum']));
// but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
expectType<Array<string | number>>(init(['fi', 1, 'fum'] as Array<string | number>));
// empty array literals return never
expectType<never[]>(init([]));
// but if it is typed, it will be `number[]`
expectType<number[]>(init([] as number[]));
// single entry tuples return never, since they literally have no init
expectType<[]>(init([10] as const));
// tuples return the example type of the input tuple minus the first entry
expectType<[10, '10']>(init([10, '10', 10] as const));
expectType<['10', 10]>(init(['10', 10, '10'] as const));
// typed arrays return the same type
expectType<Array<number | string>>(init([10, 'ten'] as Array<number | string>));
expectType<Array<string | number>>(init(['10', 10] as Array<string | number>));

// works correctly with isNotEmpty
const arr = [1, 2, 3, 4];

expectType<number[]>(init(arr));

if (isNotEmpty(arr)) {
expectType<number[]>(init(arr));
}
45 changes: 45 additions & 0 deletions test/isNotEmpty.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expectType } from 'tsd';

// TODO: check this import to '../es' once this function actually exists in ramda
import { isNotEmpty } from '../types/isNotEmpty';
import { ReadonlyNonEmptyArray, NonEmptyArray } from '../es';


// test the type narrowing
const readonlyArr: readonly number[] = [];
if (isNotEmpty(readonlyArr)) {
expectType<ReadonlyNonEmptyArray<number>>(readonlyArr);
}

const readonlyArr2: readonly number[] = [];
if (!isNotEmpty(readonlyArr2)) {
// no-op
} else {
expectType<ReadonlyNonEmptyArray<number>>(readonlyArr2);
}


const arr: number[] = [];
if (isNotEmpty(arr)) {
expectType<NonEmptyArray<number>>(arr);
}

const arr2: number[] = [];
if (!isNotEmpty(arr2)) {
// no-op
} else {
expectType<NonEmptyArray<number>>(arr2);
}


// tuples retain their type
const tuple: [number, string] = [1, '1'];
if (isNotEmpty(tuple)) {
expectType<[number, string]>(tuple);
}

// `as const` retain their type
const tuple2 = [1, 2, 3] as const;
if (isNotEmpty(tuple2)) {
expectType<readonly [1, 2, 3]>(tuple2);
}
62 changes: 62 additions & 0 deletions test/last.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expectType } from 'tsd';
import { last } from '../es';
import { isNotEmpty } from '../types/isNotEmpty';

// strings always return `string | undefined`, can't determine "emptiness" like you can with arrays
expectType<string | undefined>(last(''));
// string always return string
expectType<string | undefined>(last('abc'));

// array literals will read the type of the first entry
expectType<string>(last(['fi', 1, 'fum']));
// empty array literals return undefined
expectType<undefined>(last([]));

// typed empty array will be `T | undefined`
expectType<number | undefined>(last([] as number[]));
// as will a typed populated array
expectType<number | undefined>(last([1, 2, 3] as number[]));

// const tuples return the literal type of the last entry
expectType<10>(last([10] as const));
expectType<'ten'>(last([10, 'ten'] as const));
expectType<10>(last(['10', 10] as const));
// typed tuples return the type of the last element
expectType<boolean>(last([true] as [boolean]));
expectType<string>(last([true, 10, 'ten'] as [boolean, number, string]));
expectType<number>(last([false, '10', 10] as [boolean, string, number]));
// typed empty tuple returns undefined, this is expected because there is no `T` here
expectType<undefined>(last([] as []));

// typed arrays return `T | undefined`
expectType<number | string | undefined>(last([10, 'ten'] as Array<number | string>));
expectType<string | number | undefined>(last(['10', 10] as Array<string | number>));
expectType<number | string | undefined>(last([10, 'ten'] as ReadonlyArray<number | string>));
expectType<string | number | undefined>(last(['10', 10] as ReadonlyArray<string | number>));

// cross function testing with isNotEmpty
// test the type narrowing
const readonlyArr: readonly number[] = [];
if (isNotEmpty(readonlyArr)) {
expectType<number>(last(readonlyArr));
}

const readonlyArr2: readonly number[] = [];
if (!isNotEmpty(readonlyArr2)) {
// no-op
} else {
expectType<number>(last(readonlyArr2));
}


const arr: number[] = [];
if (isNotEmpty(arr)) {
expectType<number>(last(arr));
}

const arr2: number[] = [];
if (!isNotEmpty(arr2)) {
// no-op
} else {
expectType<number>(last(arr2));
}
16 changes: 14 additions & 2 deletions test/tail.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { expectType } from 'tsd';

// TODO: check this import to '../es' once this function actually exists in ramda
import { isNotEmpty } from '../types/isNotEmpty';
import { tail } from '../es';

// string always return string
Expand All @@ -11,14 +14,23 @@ expectType<[number, string]>(tail(['fi', 1, 'fum']));
// but if the array is typed as an `Array<T> or T[]`, then return type will be `T`
expectType<Array<string | number>>(tail(['fi', 1, 'fum'] as Array<string | number>));
// empty array literals return never
expectType<never>(tail([]));
expectType<never[]>(tail([]));
// but if it is typed, it will be `number[]`
expectType<number[]>(tail([] as number[]));
// single entry tuples return never, since they literally have no tail
expectType<never>(tail([10] as const));
expectType<[]>(tail([10] as const));
// tuples return the example type of the input tuple minus the first entry
expectType<['10', 10]>(tail([10, '10', 10] as const));
expectType<[10, '10']>(tail(['10', 10, '10'] as const));
// typed arrays return the same type
expectType<Array<number | string>>(tail([10, 'ten'] as Array<number | string>));
expectType<Array<string | number>>(tail(['10', 10] as Array<string | number>));

// works correctly with isNotEmpty
const arr = [1, 2, 3, 4];

expectType<number[]>(tail(arr));

if (isNotEmpty(arr)) {
expectType<number[]>(tail(arr));
}
16 changes: 8 additions & 8 deletions types/head.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// string
export function head(str: string): string;
// empty tuple - purposefully `never`. `head` should never work on tuple type with no length
export function head(list: readonly []): never;
// non-empty tuple
export function head<T1, TRest>(list: readonly [T1, ...TRest[]]): T1;
// arrays, because these could be empty, they return `T | undefined`
// this is no different than the tuple form since `T[]` can be empty at runtime
import { ReadonlyNonEmptyArray } from '../es';

export function head(str: string): string | undefined;
// non-empty tuple - Readonly here catches regular tuples too
export function head<T>(list: readonly [T, ...any[]]): T;
// non-empty arrays - Readonly here catches regular arrays too
export function head<T>(list: ReadonlyNonEmptyArray<T>): T;
// arrays, because these could be empty, they return `T | undefined
export function head<T>(list: readonly T[]): T | undefined;
11 changes: 10 additions & 1 deletion types/init.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
import { ReadonlyNonEmptyArray, NonEmptyArray } from '../es';

// string
export function init(list: string): string;
export function init<T>(list: readonly T[]): T[];
// `infer Init` only works on types like `readonly [1, '2', 3]` where you will get back `['2', 3]`
// else, if the type is `string[]`, you'll get back `string[]`
export function init<T extends readonly [...any]>(list: T):
T extends readonly [...infer Init, any] ? Init :
T extends ReadonlyNonEmptyArray<infer A> ? A[] :
T extends NonEmptyArray<infer A> ? A[] : T;

Loading
Loading