Skip to content

Commit 36b85ea

Browse files
authored
Merge pull request #229 from gvergnaud/add-exists-pattern
feat: Add P.nonNullable patterns
2 parents ccb52b5 + f73c103 commit 36b85ea

File tree

5 files changed

+66
-15
lines changed

5 files changed

+66
-15
lines changed

README.md

+19-4
Original file line numberDiff line numberDiff line change
@@ -792,14 +792,14 @@ type Input =
792792
| [number, '*', number]
793793
| ['-', number];
794794

795-
const input: Input = [3, '*', 4];
795+
const input = [3, '*', 4] as Input;
796796

797797
const output = match(input)
798798
.with([P._, '+', P._], ([x, , y]) => x + y)
799799
.with([P._, '-', P._], ([x, , y]) => x - y)
800800
.with([P._, '*', P._], ([x, , y]) => x * y)
801801
.with(['-', P._], ([, x]) => -x)
802-
.otherwise(() => NaN);
802+
.exhaustive();
803803

804804
console.log(output);
805805
// => 12
@@ -896,14 +896,29 @@ const input = null;
896896
const output = match<number | null | undefined>(input)
897897
.with(P.number, () => 'it is a number!')
898898
.with(P.nullish, () => 'it is either null or undefined!')
899-
.with(null, () => 'it is null!')
900-
.with(undefined, () => 'it is undefined!')
901899
.exhaustive();
902900

903901
console.log(output);
904902
// => 'it is either null or undefined!'
905903
```
906904

905+
#### `P.nonNullable` wildcard
906+
907+
The `P.nonNullable` pattern will match any value except `null` or `undefined`.
908+
909+
```ts
910+
import { match, P } from 'ts-pattern';
911+
912+
const input = null;
913+
914+
const output = match<number | null | undefined>(input)
915+
.with(P.nonNullable, () => 'it is a number!')
916+
.otherwise(() => 'it is either null or undefined!');
917+
918+
console.log(output);
919+
// => 'it is either null or undefined!'
920+
```
921+
907922
#### `P.bigint` wildcard
908923

909924
The `P.bigint` pattern will match any value of type `bigint`.

docs/roadmap.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
### Roadmap
22

3+
- [ ] `P.array.includes(x)`
4+
- [ ] `P.record({Pkey}, {Pvalue})`
5+
- [x] `P.nonNullable`
36
- [x] chainable methods
47
- [x] string
58
- [x] `P.string.includes('str')`

src/patterns.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
StringChainable,
3535
ArrayChainable,
3636
Variadic,
37+
NonNullablePattern,
3738
} from './types/Pattern';
3839

3940
export type { Pattern, Fn as unstable_Fn };
@@ -634,6 +635,10 @@ function isNullish<T>(x: T | null | undefined): x is null | undefined {
634635
return x === null || x === undefined;
635636
}
636637

638+
function isNonNullable(x: unknown): x is {} {
639+
return x !== null && x !== undefined;
640+
}
641+
637642
type AnyConstructor = abstract new (...args: any[]) => any;
638643

639644
function isInstanceOf<T extends AnyConstructor>(classConstructor: T) {
@@ -928,7 +933,7 @@ export const number: NumberPattern = numberChainable(when(isNumber));
928933
*
929934
* @example
930935
* match(value)
931-
* .with(P.bigint.between(0, 10), () => '0 <= numbers <= 10')
936+
* .with(P.bigint.between(0, 10), () => '0 <= bigints <= 10')
932937
*/
933938
const betweenBigInt = <
934939
input,
@@ -947,7 +952,7 @@ const betweenBigInt = <
947952
*
948953
* @example
949954
* match(value)
950-
* .with(P.bigint.lt(10), () => 'numbers < 10')
955+
* .with(P.bigint.lt(10), () => 'bigints < 10')
951956
*/
952957
const ltBigInt = <input, const max extends bigint>(
953958
max: max
@@ -961,7 +966,7 @@ const ltBigInt = <input, const max extends bigint>(
961966
*
962967
* @example
963968
* match(value)
964-
* .with(P.bigint.gt(10), () => 'numbers > 10')
969+
* .with(P.bigint.gt(10), () => 'bigints > 10')
965970
*/
966971
const gtBigInt = <input, const min extends bigint>(
967972
min: min
@@ -1072,10 +1077,20 @@ export const symbol: SymbolPattern = chainable(when(isSymbol));
10721077
* [Read the documentation for `P.nullish` on GitHub](https://github.com/gvergnaud/ts-pattern#nullish-wildcard)
10731078
*
10741079
* @example
1075-
* .with(P.nullish, () => 'will match on null or undefined')
1080+
* .with(P.nullish, (x) => `${x} is null or undefined`)
10761081
*/
10771082
export const nullish: NullishPattern = chainable(when(isNullish));
10781083

1084+
/**
1085+
* `P.nonNullable` is a wildcard pattern, matching everything except **null** or **undefined**.
1086+
*
1087+
* [Read the documentation for `P.nonNullable` on GitHub](https://github.com/gvergnaud/ts-pattern#nonNullable-wildcard)
1088+
*
1089+
* @example
1090+
* .with(P.nonNullable, (x) => `${x} isn't null nor undefined`)
1091+
*/
1092+
export const nonNullable: NonNullablePattern = chainable(when(isNonNullable));
1093+
10791094
/**
10801095
* `P.instanceOf(SomeClass)` is a pattern matching instances of a given class.
10811096
*

src/types/Pattern.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import type * as symbols from '../internals/symbols';
2-
import {
3-
LeastUpperBound,
4-
MergeUnion,
5-
Primitives,
6-
WithDefault,
7-
} from './helpers';
2+
import { MergeUnion, Primitives, WithDefault } from './helpers';
83
import { None, Some, SelectionType } from './FindSelected';
9-
import { matcher, narrow } from '../patterns';
4+
import { matcher } from '../patterns';
105
import { ExtractPreciseValue } from './ExtractPreciseValue';
116

127
export type MatcherType =
@@ -192,6 +187,8 @@ export type NullishPattern = Chainable<
192187
never
193188
>;
194189

190+
export type NonNullablePattern = Chainable<GuardP<unknown, {}>, never>;
191+
195192
type MergeGuards<input, guard1, guard2> = [guard1, guard2] extends [
196193
GuardExcludeP<any, infer narrowed1, infer excluded1>,
197194
GuardExcludeP<any, infer narrowed2, infer excluded2>

tests/wildcards.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,27 @@ describe('wildcards', () => {
5656
expect(res2).toEqual(true);
5757
});
5858

59+
it('should match nonNullable wildcard', () => {
60+
type Input = string | number | boolean | null | undefined;
61+
const res = match<Input>(false)
62+
.with(P.nonNullable, (x) => {
63+
type t = Expect<Equal<typeof x, string | number | boolean>>;
64+
return true;
65+
})
66+
.otherwise(() => false);
67+
68+
const res2 = match<0 | 1 | 2 | null>(0)
69+
.with(P.nonNullable, (x) => {
70+
type t = Expect<Equal<typeof x, 0 | 1 | 2>>;
71+
return true;
72+
})
73+
.with(null, () => false)
74+
.exhaustive();
75+
76+
expect(res).toEqual(true);
77+
expect(res2).toEqual(true);
78+
});
79+
5980
it('should match String, Number and Boolean wildcards', () => {
6081
// Will be { id: number, title: string } | { errorMessage: string }
6182
let httpResult = {

0 commit comments

Comments
 (0)