Skip to content

Commit

Permalink
feat(P.object): Add P.object.exact
Browse files Browse the repository at this point in the history
  • Loading branch information
gvergnaud committed Mar 31, 2024
1 parent e6bf0af commit 6cc4989
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
35 changes: 33 additions & 2 deletions src/patterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
ObjectChainable,
ObjectPattern,
EmptyObjectPattern,
ObjectLiteralPattern,
} from './types/Pattern';

export type { Pattern, Fn as unstable_Fn };
Expand Down Expand Up @@ -164,6 +165,7 @@ function objectChainable<pattern extends Matcher<any, any, any, any, any>>(
): ObjectChainable<pattern> {
return Object.assign(chainable(pattern), {
empty: () => emptyObject,
exact: exactObject,
}) as any;
}

Expand Down Expand Up @@ -654,13 +656,17 @@ function isObject(x: unknown): x is object {
return !!x && (typeof x === 'object' || typeof x === 'function');
}

function isEmptyObject(x: unknown) {
function hasExactKeys(keys: Set<PropertyKey>, x: unknown) {
if (!x || typeof x !== 'object') return false;
if (Array.isArray(x)) return false;
for (const _key in x) return false;
for (const key in x) if (!keys.has(key)) return false;
return true;
}

function isEmptyObject(x: unknown) {
return hasExactKeys(new Set(), x);
}

type AnyConstructor = abstract new (...args: any[]) => any;

function isInstanceOf<T extends AnyConstructor>(classConstructor: T) {
Expand Down Expand Up @@ -1124,6 +1130,31 @@ export const nonNullable: NonNullablePattern = chainable(when(isNonNullable));
*/
const emptyObject: EmptyObjectPattern = chainable(when(isEmptyObject));

/**
* `P.object.exact({...})` matching objects that contain exactly the set of defined in the pattern. Objects with additional keys won't match this pattern, even if keys defined in both the pattern and the object match.
*
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
*
* @example
* match(value)
* .with(
* P.object.exact({ a: P.any }),
* () => 'Objects with a single `a` key that contains anything.'
* )
*/
export function exactObject<
input,
const pattern extends ObjectLiteralPattern<input>
>(
pattern: pattern
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
export function exactObject(pattern: ObjectLiteralPattern<{}>) {
const patternKeys = new Set(Object.keys(pattern));
return chainable(
when((input) => isMatching(pattern) && hasExactKeys(patternKeys, input))
);
}

/**
* `P.object` is a wildcard pattern, matching any **object**.
*
Expand Down
19 changes: 18 additions & 1 deletion src/types/Pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MergeUnion, Primitives, WithDefault } from './helpers';
import { None, Some, SelectionType } from './FindSelected';
import { matcher } from '../patterns';
import { ExtractPreciseValue } from './ExtractPreciseValue';
import { InvertPattern } from './InvertPattern';

export type MatcherType =
| 'not'
Expand Down Expand Up @@ -164,7 +165,7 @@ type KnownPatternInternal<
: ObjectLiteralPattern<Readonly<MergeUnion<objs>>>)
| ([arrays] extends [never] ? never : ArrayPattern<arrays>);

type ObjectLiteralPattern<a> =
export type ObjectLiteralPattern<a> =
| {
readonly [k in keyof a]?: Pattern<a[k]>;
}
Expand Down Expand Up @@ -681,6 +682,22 @@ export type ObjectChainable<
* .with(P.object.empty(), () => 'will match on empty objects')
*/
empty: () => EmptyObjectPattern;

/**
* `P.object.exact({...})` matching objects that contain exactly the set of defined in the pattern. Objects with additional keys won't match this pattern, even if keys defined in both the pattern and the object match.
*
* [Read the documentation for `P.object.exact()` on GitHub](https://github.com/gvergnaud/ts-pattern#pobjectexact)
*
* @example
* match(value)
* .with(
* P.object.exact({ a: P.any }),
* () => 'Objects with a single `a` key that contains anything.'
* )
*/
<input, const pattern extends ObjectLiteralPattern<input>>(
pattern: pattern
): Chainable<GuardExcludeP<input, InvertPattern<pattern, input>, never>>;
},
omitted
>;

0 comments on commit 6cc4989

Please sign in to comment.