Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: apply filtering to types when selecting a leaf node #526

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Loading