Skip to content

Commit 9f35162

Browse files
committed
chainable: add a few string chainable methods
1 parent 0b92728 commit 9f35162

3 files changed

Lines changed: 130 additions & 35 deletions

File tree

docs/roadmap.md

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
11
### Roadmap
22

33
- [ ] chainable methods
4-
5-
- string
6-
- `P.string.includes('str')`
7-
- `P.string.startsWith('str')`
8-
- `P.string.endsWith('str')`
9-
- `P.string.regex('[a-z]+')`
10-
- numbers
11-
- `P.number.between(1, 10)`
12-
- `P.number.lt(12)`
13-
- `P.number.gt(12)`
14-
- `P.number.gte(12)`
15-
- `P.number.lte(12)`
16-
- `P.number.int(12)`
17-
- `P.number.finite`
18-
- `P.number.positive`
19-
- `P.number.negative`
20-
- all
21-
- `P.number.optional`
22-
- `P.string.optional`
23-
- `P.number.select()`
24-
- `P.string.select()`
25-
- `P.number.optional.select()`
26-
- `P.string.optional.select()`
27-
4+
- [ ] string
5+
- [x] `P.string.includes('str')`
6+
- [x] `P.string.startsWith('str')`
7+
- [x] `P.string.endsWith('str')`
8+
- [ ] `P.string.regex('[a-z]+')`
9+
- [ ] numbers
10+
- [ ] `P.number.between(1, 10)`
11+
- [ ] `P.number.lt(12)`
12+
- [ ] `P.number.gt(12)`
13+
- [ ] `P.number.gte(12)`
14+
- [ ] `P.number.lte(12)`
15+
- [ ] `P.number.int(12)`
16+
- [ ] `P.number.finite`
17+
- [ ] `P.number.positive`
18+
- [ ] `P.number.negative`
19+
- [ ] all
20+
- [ ] `P.number.optional`
21+
- [ ] `P.string.optional`
22+
- [ ] `P.number.select()`
23+
- [ ] `P.string.select()`
24+
- [ ] `P.number.optional.select()`
25+
- [ ] `P.string.optional.select()`
2826
- [x] Add a custom matcher protocol data structures could implement to make them matchable.
2927
- [x] (Maybe) add an iterator protocol to `P.array` to be usable as a variadic tuple pattern. Example of using `P.array`:
3028

src/patterns.ts

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -593,19 +593,68 @@ export const any = when(isUnknown);
593593
export const _ = any;
594594

595595
/**
596-
* `P.string` is a wildcard pattern matching any **string**.
596+
* `P.string.startsWith(start)` is a pattern, matching **strings** starting with `start`.
597+
*
598+
* [Read documentation for `P.string.startsWith` on GitHub](https://github.com/gvergnaud/ts-pattern#PstringstartsWith)
599+
*
600+
* @example
601+
* match(value)
602+
* .with(P.string.startsWith('A'), () => 'value starts with an A')
603+
*/
604+
605+
const startsWith = <input, const start extends string>(
606+
start: start
607+
): GuardP<input, `${start}${string}`> =>
608+
when((value) => isString(value) && value.startsWith(start));
609+
610+
/**
611+
* `P.string.endsWith(end)` is a pattern, matching **strings** ending with `end`.
612+
*
613+
* [Read documentation for `P.string.endsWith` on GitHub](https://github.com/gvergnaud/ts-pattern#PstringendsWith)
614+
*
615+
* @example
616+
* match(value)
617+
* .with(P.string.endsWith('!'), () => 'value ends with an !')
618+
*/
619+
const endsWith = <input, const end extends string>(
620+
end: end
621+
): GuardP<input, `${string}${end}`> =>
622+
when((value) => isString(value) && value.endsWith(end));
623+
624+
/**
625+
* `P.string.includes(substr)` is a pattern, matching **strings** containing `substr`.
626+
*
627+
* [Read documentation for `P.string.includes` on GitHub](https://github.com/gvergnaud/ts-pattern#Pstringincludes)
628+
*
629+
* @example
630+
* match(value)
631+
* .with(P.string.includes('http'), () => 'value contains http')
632+
*/
633+
const includes = <input, const substr extends string>(
634+
substr: substr
635+
): GuardExcludeP<input, string, never> =>
636+
when((value) => isString(value) && value.includes(substr));
637+
638+
const assignStringMethods = <p extends GuardP<any, any>>(pattern: p) =>
639+
Object.assign(pattern, {
640+
startsWith,
641+
endsWith,
642+
includes,
643+
});
644+
645+
/**
646+
* `P.string` is a wildcard pattern, matching any **string**.
597647
*
598648
* [Read documentation for `P.string` on GitHub](https://github.com/gvergnaud/ts-pattern#Pstring-wildcard)
599649
*
600650
* @example
601651
* match(value)
602652
* .with(P.string, () => 'will match on strings')
603653
*/
604-
605-
export const string = when(isString);
654+
export const string = assignStringMethods(when(isString));
606655

607656
/**
608-
* `P.number` is a wildcard pattern matching any **number**.
657+
* `P.number` is a wildcard pattern, matching any **number**.
609658
*
610659
* [Read documentation for `P.number` on GitHub](https://github.com/gvergnaud/ts-pattern#Pnumber-wildcard)
611660
*
@@ -616,7 +665,7 @@ export const string = when(isString);
616665
export const number = when(isNumber);
617666

618667
/**
619-
* `P.boolean` is a wildcard pattern matching any **boolean**.
668+
* `P.boolean` is a wildcard pattern, matching any **boolean**.
620669
*
621670
* [Read documentation for `P.boolean` on GitHub](https://github.com/gvergnaud/ts-pattern#boolean-wildcard)
622671
*
@@ -626,7 +675,7 @@ export const number = when(isNumber);
626675
export const boolean = when(isBoolean);
627676

628677
/**
629-
* `P.bigint` is a wildcard pattern matching any **bigint**.
678+
* `P.bigint` is a wildcard pattern, matching any **bigint**.
630679
*
631680
* [Read documentation for `P.bigint` on GitHub](https://github.com/gvergnaud/ts-pattern#bigint-wildcard)
632681
*
@@ -636,7 +685,7 @@ export const boolean = when(isBoolean);
636685
export const bigint = when(isBigInt);
637686

638687
/**
639-
* `P.symbol` is a wildcard pattern matching any **symbol**.
688+
* `P.symbol` is a wildcard pattern, matching any **symbol**.
640689
*
641690
* [Read documentation for `P.symbol` on GitHub](https://github.com/gvergnaud/ts-pattern#symbol-wildcard)
642691
*
@@ -646,7 +695,7 @@ export const bigint = when(isBigInt);
646695
export const symbol = when(isSymbol);
647696

648697
/**
649-
* `P.nullish` is a wildcard pattern matching **null** or **undefined**.
698+
* `P.nullish` is a wildcard pattern, matching **null** or **undefined**.
650699
*
651700
* [Read documentation for `P.nullish` on GitHub](https://github.com/gvergnaud/ts-pattern#nullish-wildcard)
652701
*

tests/chainable.test.ts

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,58 @@
1+
import { P, match } from '../src';
2+
import { Equal, Expect } from '../src/types/helpers';
3+
14
describe('chainable methods', () => {
25
describe('string', () => {
3-
it(`P.string.includes('str')`, () => {});
4-
it(`P.string.startsWith('str')`, () => {});
5-
it(`P.string.endsWith('str')`, () => {});
6+
it(`P.string.includes('str')`, () => {
7+
const f = (input: string | number) =>
8+
match(input)
9+
.with(P.string.includes('!!'), (value) => {
10+
type t = Expect<Equal<typeof value, string>>;
11+
return 'includes !!';
12+
})
13+
.otherwise((value) => {
14+
type t = Expect<Equal<typeof value, string | number>>;
15+
return 'something else';
16+
});
17+
18+
expect(f('hello!!')).toBe('includes !!');
19+
expect(f('nope')).toBe('something else');
20+
});
21+
22+
it(`P.string.startsWith('str')`, () => {
23+
const f = (input: string | number) =>
24+
match(input)
25+
.with(P.string.startsWith('hello '), (value) => {
26+
type t = Expect<Equal<typeof value, `hello ${string}`>>;
27+
return 'starts with hello';
28+
})
29+
.otherwise((value) => {
30+
type t = Expect<Equal<typeof value, string | number>>;
31+
return 'something else';
32+
});
33+
34+
expect(f('hello gabriel')).toBe('starts with hello');
35+
expect(f('gabriel')).toBe('something else');
36+
});
37+
38+
it(`P.string.endsWith('str')`, () => {
39+
const f = (input: string | number) =>
40+
match(input)
41+
.with(P.string.endsWith('!!'), (value) => {
42+
type t = Expect<Equal<typeof value, `${string}!!`>>;
43+
return 'ends with !!';
44+
})
45+
.otherwise((value) => {
46+
type t = Expect<Equal<typeof value, string | number>>;
47+
return 'something else';
48+
});
49+
50+
expect(f('hello!!')).toBe('ends with !!');
51+
expect(f('nope')).toBe('something else');
52+
});
653
it(`P.string.regex('[a-z]+')`, () => {});
754
});
55+
856
describe('number', () => {
957
it(`P.number.between(1, 10)`, () => {});
1058
it(`P.number.lt(12)`, () => {});

0 commit comments

Comments
 (0)