Skip to content

Commit 854c582

Browse files
authored
improve object/has types
1 parent 65e6a3b commit 854c582

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

src/object/__tests__/has.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expectType } from 'ts-expect';
12
import has from '../has';
23

34
describe('utils/object/has', () => {
@@ -14,4 +15,56 @@ describe('utils/object/has', () => {
1415
C.prototype = { a: 5 };
1516
expect(has('a', new C())).toBe(false);
1617
});
18+
19+
it('should have `false` type for `undefined` and `null` inputs', () => {
20+
expectType<false>(has('a', null));
21+
expectType<false>(has('a')(null));
22+
expectType<false>(has('a', undefined));
23+
expectType<false>(has('a')(undefined));
24+
});
25+
26+
it('should exclude `null` and `undefined` from union', () => {
27+
const something = { id: 12 } as { id: number } | null | undefined;
28+
29+
if (has('name', something)) {
30+
expectType<{ id: number; name: unknown }>(something);
31+
}
32+
33+
const hasId = has('id');
34+
if(hasId(something)) {
35+
expectType<{ id: number }>(something);
36+
}
37+
});
38+
39+
it('should narrow union', () => {
40+
const something = { id: 12 } as { id: number } | { name: string } | { name: string; somethingElse: string };
41+
42+
if (has('name', something)) {
43+
expectType<{ name: string } | { name: string; somethingElse: string }>(something);
44+
}
45+
const hasName = has('name');
46+
if (hasName(something)) {
47+
expectType<{ name: string } | { name: string; somethingElse: string }>(something);
48+
}
49+
50+
if (has('id', something)) {
51+
expectType<{ id: number }>(something);
52+
}
53+
const hasId = has('id');
54+
if (hasId(something)) {
55+
expectType<{ id: number }>(something);
56+
}
57+
});
58+
59+
it('should add missing property as `unknown`', () => {
60+
const something = { id: 12 } as { id: number } | { name: string };
61+
62+
if (has('missing', something)) {
63+
expectType<({ name: string } | { id: number }) & { missing: unknown }>(something);
64+
}
65+
const hasMissing = has('missing');
66+
if (hasMissing(something)) {
67+
expectType<({ name: string } | { id: number }) & { missing: unknown }>(something);
68+
}
69+
});
1770
});

src/object/has.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import curryN from '../function/curryN';
22
import { Prop } from '../typings/types';
33

4+
interface HasPredicate<P extends Prop> {
5+
(obj: undefined | null): false;
6+
<T extends { [K in P]: unknown }, U>(obj: U | T): obj is T;
7+
<T extends object>(obj: T): obj is T & Record<P, unknown>;
8+
}
9+
410
interface Has {
5-
<K extends Prop>(s: K, obj): boolean;
6-
<K extends Prop>(s: K): (obj) => boolean;
11+
<P extends Prop>(prop: P): HasPredicate<P>
12+
(prop: Prop, obj: undefined | null): false;
13+
<P extends Prop, T extends { [K in P]: unknown }, U>(prop: P, obj: U | T): obj is T;
14+
<P extends Prop, T extends object>(prop: P, obj: T): obj is T & Record<P, unknown>;
715
}
816

917
/**

0 commit comments

Comments
 (0)