Skip to content

Commit

Permalink
Make assert, truthy and falsy typeguards
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
ZachHaber and sindresorhus committed Aug 16, 2023
1 parent e58f466 commit e27183a
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 3 deletions.
28 changes: 28 additions & 0 deletions test-types/import-in-cts/assertions-as-type-guards.cts
Expand Up @@ -4,6 +4,15 @@ import {expectType} from 'tsd';
type Expected = {foo: 'bar'};
const expected: Expected = {foo: 'bar'};

test('assert', t => {
const actual = expected as Expected | undefined;
if (t.truthy(actual)) {
expectType<Expected>(actual);
} else {
expectType<undefined>(actual);
}
});

test('deepEqual', t => {
const actual: unknown = {};
if (t.deepEqual(actual, expected)) {
Expand Down Expand Up @@ -32,9 +41,28 @@ test('false', t => {
}
});

test('falsy', t => {
type Actual = Expected | undefined | false | 0 | '' | 0n;
const actual = undefined as Actual;
if (t.falsy(actual)) {
expectType<Exclude<Actual, Expected>>(actual);
} else {
expectType<Expected>(actual);
}
});

test('true', t => {
const actual: unknown = false;
if (t.true(actual)) {
expectType<true>(actual);
}
});

test('truthy', t => {
const actual = expected as Expected | undefined;
if (t.truthy(actual)) {
expectType<Expected>(actual);
} else {
expectType<undefined>(actual);
}
});
28 changes: 28 additions & 0 deletions test-types/module/assertions-as-type-guards.ts
Expand Up @@ -5,6 +5,15 @@ import test from '../../entrypoints/main.mjs';
type Expected = {foo: 'bar'};
const expected: Expected = {foo: 'bar'};

test('assert', t => {
const actual = expected as Expected | undefined;
if (t.truthy(actual)) {
expectType<Expected>(actual);
} else {
expectType<undefined>(actual);
}
});

test('deepEqual', t => {
const actual: unknown = {};
if (t.deepEqual(actual, expected)) {
Expand Down Expand Up @@ -33,9 +42,28 @@ test('false', t => {
}
});

test('falsy', t => {
type Actual = Expected | undefined | false | 0 | '' | 0n;
const actual = undefined as Actual;
if (t.falsy(actual)) {
expectType<Exclude<Actual, Expected>>(actual);
} else {
expectType<Expected>(actual);
}
});

test('true', t => {
const actual: unknown = false;
if (t.true(actual)) {
expectType<true>(actual);
}
});

test('truthy', t => {
const actual = expected as Expected | undefined;
if (t.truthy(actual)) {
expectType<Expected>(actual);
} else {
expectType<undefined>(actual);
}
});
17 changes: 14 additions & 3 deletions types/assertions.d.cts
Expand Up @@ -27,6 +27,8 @@ export type Assertions = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*
* Note: An `else` clause using this as a type guard will be subtly incorrect for `string` and `number` types and will not give `0` or `''` as a potential value in an `else` clause.
*/
assert: AssertAssertion;

Expand Down Expand Up @@ -121,16 +123,23 @@ export type Assertions = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*
* Note: An `else` clause using this as a type guard will be subtly incorrect for `string` and `number` types and will not give `0` or `''` as a potential value in an `else` clause.
*/
truthy: TruthyAssertion;
};

type FalsyValue = false | 0 | 0n | '' | null | undefined;
type Falsy<T> = T extends Exclude<T, FalsyValue> ? (T extends number | string | bigint ? T & FalsyValue : never) : T;

export type AssertAssertion = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*
* Note: An `else` clause using this as a type guard will be subtly incorrect for `string` and `number` types and will not give `0` or `''` as a potential value in an `else` clause.
*/
(actual: any, message?: string): boolean;
<T>(actual: T, message?: string): actual is T extends Falsy<T> ? never : T;

/** Skip this assertion. */
skip(actual: any, message?: string): void;
Expand Down Expand Up @@ -192,7 +201,7 @@ export type FalsyAssertion = {
* Assert that `actual` is [falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), returning a boolean
* indicating whether the assertion passed.
*/
(actual: any, message?: string): boolean;
<T>(actual: T, message?: string): actual is Falsy<T>;

/** Skip this assertion. */
skip(actual: any, message?: string): void;
Expand Down Expand Up @@ -336,8 +345,10 @@ export type TruthyAssertion = {
/**
* Assert that `actual` is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy), returning a boolean
* indicating whether the assertion passed.
*
* Note: An `else` clause using this as a type guard will be subtly incorrect for `string` and `number` types and will not give `0` or `''` as a potential value in an `else` clause.
*/
(actual: any, message?: string): boolean;
<T>(actual: T, message?: string): actual is T extends Falsy<T> ? never : T;

/** Skip this assertion. */
skip(actual: any, message?: string): void;
Expand Down

0 comments on commit e27183a

Please sign in to comment.