Skip to content

Commit

Permalink
fix: apply filtering to types when selecting a leaf node
Browse files Browse the repository at this point in the history
fixes #524
  • Loading branch information
RebeccaStevens committed Jan 23, 2025
1 parent 98400c3 commit 79f002a
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 10 deletions.
48 changes: 43 additions & 5 deletions src/types/merging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ import type {
DeepMergeRecordsDefaultHKT,
DeepMergeSetsDefaultHKT,
} from "./defaults";
import type { EveryIsArray, EveryIsMap, EveryIsRecord, EveryIsSet, IsNever, IsTuple } from "./utils";
import type {
AssertType,
EveryIsArray,
EveryIsMap,
EveryIsRecord,
EveryIsSet,
IsNever,
IsTuple,
TupleTupleToTupleUnion,
UnionToTuple,
} from "./utils";

/**
* Mapping of merge function URIs to the merge function type.
Expand All @@ -16,7 +26,7 @@ export interface DeepMergeFunctionURItoKind<
Fs extends DeepMergeFunctionsURIs,
in out M,
> {
readonly DeepMergeLeafURI: DeepMergeLeaf<Ts>;
readonly DeepMergeLeafURI: DeepMergeLeaf<Ts, Fs, M>;
readonly DeepMergeRecordsDefaultURI: DeepMergeRecordsDefaultHKT<Ts, Fs, M>;
readonly DeepMergeArraysDefaultURI: DeepMergeArraysDefaultHKT<Ts, Fs, M>;
readonly DeepMergeSetsDefaultURI: DeepMergeSetsDefaultHKT<Ts>;
Expand Down Expand Up @@ -174,18 +184,46 @@ export type DeepMergeNoFilteringURI = "DeepMergeNoFilteringURI";
/**
* Get the leaf type from many types that can't be merged.
*/
export type DeepMergeLeaf<Ts extends ReadonlyArray<unknown>> = Ts extends readonly []
export type DeepMergeLeaf<
Ts extends ReadonlyArray<unknown>,
Fs extends DeepMergeFunctionsURIs,
M,
> = Ts extends readonly []
? never
: Ts extends readonly [infer T]
? T
: Ts extends readonly [...infer Rest, infer Tail]
? IsNever<Tail> extends true
? Rest extends ReadonlyArray<unknown>
? DeepMergeLeaf<Rest>
? DeepMergeLeaf<Rest, Fs, M>
: never
: Tail
: DeepMergeLeafApplyFilter<
Ts,
AssertType<
ReadonlyArray<unknown>,
TupleTupleToTupleUnion<
AssertType<
ReadonlyArray<ReadonlyArray<unknown>>,
{
[I in keyof Ts]: FilterValuesHKT<UnionToTuple<Ts[I]>, Fs, M>;
}
>
>
>
>
: never;

type DeepMergeLeafApplyFilter<
Original extends ReadonlyArray<unknown>,
Filtered extends ReadonlyArray<unknown>,
> = Original extends readonly [...infer OriginalRest, infer OriginalTail]
? Filtered extends readonly [...infer FilteredRest, infer FilteredTail]
? OriginalTail extends FilteredTail
? FilteredTail
: FilteredTail | DeepMergeLeafApplyFilter<OriginalRest, FilteredRest>
: never
: never;

/**
* The meta data deepmerge is able to provide.
*/
Expand Down
21 changes: 21 additions & 0 deletions src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,24 @@ export type UnionToTuple<T, L = LastOf<T>> = IsNever<T> extends true ? [] : [...
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => infer R ? R : never;

/**
* Convert a tuple of tuples to a tuple of unions.
*/
export type TupleTupleToTupleUnion<T extends ReadonlyArray<ReadonlyArray<unknown>>> = {
[I in keyof T]: TupleToUnion<T[I]>;
};

/**
* Convert a tuple to a union.
*/
export type TupleToUnion<T extends ReadonlyArray<unknown>> = T extends readonly []
? never
: T extends readonly [infer Head, ...infer Rest]
? Head | TupleToUnion<Rest>
: never;

/**
* Assert that a type is of a given type.
*/
export type AssertType<Expected, T> = T extends Expected ? T : never;
8 changes: 4 additions & 4 deletions tests/deepmerge-custom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ type Entries<T> = Array<

declare module "../src/types" {
interface DeepMergeFunctionURItoKind<Ts extends ReadonlyArray<unknown>, Fs extends DeepMergeFunctionsURIs, M> {
readonly NoArrayMerge1: DeepMergeLeaf<Ts>;
readonly NoArrayMerge1: DeepMergeLeaf<Ts, Fs, M>;
}
}

declare module "../src/types" {
interface DeepMergeFunctionURItoKind<Ts extends ReadonlyArray<unknown>, Fs extends DeepMergeFunctionsURIs, M> {
readonly MergeDates1: EveryIsDate<Ts> extends true ? Ts : DeepMergeLeaf<Ts>;
readonly MergeDates1: EveryIsDate<Ts> extends true ? Ts : DeepMergeLeaf<Ts, Fs, M>;
}
}

Expand All @@ -60,7 +60,7 @@ declare module "../src/types" {
Fs extends DeepMergeFunctionsURIs,
M,
> {
readonly KeyPathBasedMerge: Ts[number] extends number ? Ts[number] | string : DeepMergeLeaf<Ts>;
readonly KeyPathBasedMerge: Ts[number] extends number ? Ts[number] | string : DeepMergeLeaf<Ts, Fs, M>;
}
}

Expand All @@ -76,7 +76,7 @@ declare module "../src/types" {
Fs,
M
>
: DeepMergeLeaf<Ts>;
: DeepMergeLeaf<Ts, Fs, M>;
}
}

Expand Down
20 changes: 19 additions & 1 deletion tests/deepmerge.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { expectAssignable, expectType } from "tsd";

import { type DeepMergeMapsDefaultHKT, type DeepMergeSetsDefaultHKT, deepmerge } from "../src";
import {
type DeepMergeMapsDefaultHKT,
type DeepMergeNoFilteringURI,
type DeepMergeSetsDefaultHKT,
deepmerge,
deepmergeCustom,
} from "../src";

const a = {
foo: "abc",
Expand Down Expand Up @@ -252,3 +258,15 @@ expectType<unknown>(test22);
const r: { a?: string; b?: number; c?: boolean } = { a: "a", b: 1 };
const test23 = deepmerge(r, r);
expectType<{ a?: string; b?: number; c?: boolean }>(test23);

const s: { foo: number | undefined } = { foo: undefined };
const test24 = deepmerge(a, s);
expectType<{ foo: string | number; baz: { quux: string[] }; garply: number }>(test24);

const test25 = deepmergeCustom<
unknown,
{
DeepMergeFilterValuesURI: DeepMergeNoFilteringURI;
}
>({ filterValues: false })(a, s);
expectType<{ foo: number | undefined; baz: { quux: string[] }; garply: number }>(test25);

0 comments on commit 79f002a

Please sign in to comment.