diff --git a/source/set-required-deep.d.ts b/source/set-required-deep.d.ts index edfb3bc14..433583dea 100644 --- a/source/set-required-deep.d.ts +++ b/source/set-required-deep.d.ts @@ -1,6 +1,10 @@ +import type {IsAny} from './is-any'; import type {NonRecursiveType, StringToNumber} from './internal'; import type {Paths} from './paths'; +import type {SetRequired} from './set-required'; import type {SimplifyDeep} from './simplify-deep'; +import type {UnionToTuple} from './union-to-tuple'; +import type {RequiredDeep} from './required-deep'; import type {UnknownArray} from './unknown-array'; /** @@ -28,19 +32,37 @@ type SomeRequiredDeep = SetRequiredDeep; // d: number // Is now required // }[] // } + +// Set specific indices in an array to be required. +type ArrayExample = SetRequiredDeep<{a: [number?, number?, number?]}, 'a.0' | 'a.1'>; +//=> {a: [number, number, number?]} ``` @category Object */ -export type SetRequiredDeep> = -BaseType extends NonRecursiveType +export type SetRequiredDeep> = IsAny extends true + ? SimplifyDeep> + : SetRequiredDeepHelper>; + +/** +Internal helper for {@link SetRequiredDeep}. + +Recursively transforms the `BaseType` by applying {@link SetRequiredDeepSinglePath} for each path in `KeyPathsTuple`. +*/ +type SetRequiredDeepHelper = + KeyPathsTuple extends [infer KeyPath, ...infer RestPaths] + ? SetRequiredDeepHelper, RestPaths> + : BaseType; + +/** +Makes a single path required in `BaseType`. +*/ +type SetRequiredDeepSinglePath = BaseType extends NonRecursiveType ? BaseType - : SimplifyDeep<( - BaseType extends UnknownArray - ? {} - : {[K in keyof BaseType as K extends (KeyPaths | StringToNumber) ? K : never]-?: BaseType[K]} - ) & { - [K in keyof BaseType]: Extract extends never - ? BaseType[K] - : SetRequiredDeep}` ? Rest : never> - }>; + : KeyPath extends `${infer Property}.${infer RestPath}` + ? { + [Key in keyof BaseType]: Property extends `${Key & (string | number)}` + ? SetRequiredDeepSinglePath + : BaseType[Key]; + } + : SetRequired) & keyof BaseType>; diff --git a/test-d/set-required-deep.ts b/test-d/set-required-deep.ts index 91ff78399..7989fab8a 100644 --- a/test-d/set-required-deep.ts +++ b/test-d/set-required-deep.ts @@ -21,42 +21,164 @@ expectType<{a: number; b?: {c?: string}}>(variation4); declare const variation5: SetRequiredDeep<{a?: '1'; b?: {c?: boolean}} | {a?: '2'; b?: {c?: boolean}}, 'a'>; expectType<{a: '1'; b?: {c?: boolean}} | {a: '2'; b?: {c?: boolean}}>(variation5); -// Set array key to required +// Set key with array type to required declare const variation6: SetRequiredDeep<{a?: Array<{b?: number}>}, 'a'>; expectType<{a: Array<{b?: number}>}>(variation6); -// Set key inside array to required -declare const variation7: SetRequiredDeep<{a?: Array<{b?: number}>}, `a.${number}.b`>; -expectType<{a?: Array<{b: number}>}>(variation7); - -// Set only specified keys inside array to required -declare const variation8: SetRequiredDeep<{a?: Array<{b?: number; c?: string}>}, `a.${number}.b`>; -expectType<{a?: Array<{b: number; c?: string}>}>(variation8); - // Can set both root and nested keys to required -declare const variation9: SetRequiredDeep<{a?: number; b?: {c?: string}}, 'b' | 'b.c'>; -expectType<{a?: number; b: {c: string}}>(variation9); +declare const variation7: SetRequiredDeep<{a?: number; b?: {c?: string}}, 'b' | 'b.c'>; +expectType<{a?: number; b: {c: string}}>(variation7); // Preserves required root keys -declare const variation10: SetRequiredDeep<{a: 1; b: {c?: 1}}, 'b.c'>; -expectType<{a: 1; b: {c: 1}}>(variation10); +declare const variation8: SetRequiredDeep<{a: 1; b: {c?: 1}}, 'b.c'>; +expectType<{a: 1; b: {c: 1}}>(variation8); // Preserves union in root keys -declare const variation11: SetRequiredDeep<{a: 1; b: {c?: 1} | number}, 'b.c'>; -expectType<{a: 1; b: {c: 1} | number}>(variation11); +declare const variation9: SetRequiredDeep<{a: 1; b: {c?: 1} | number}, 'b.c'>; +expectType<{a: 1; b: {c: 1} | number}>(variation9); + +// Preserves readonly +declare const variation10: SetRequiredDeep<{a: 1; readonly b: {c?: 1}}, 'b.c'>; +expectType<{a: 1; readonly b: {c: 1}}>(variation10); -// Preserves readonly in root keys -declare const variation12: SetRequiredDeep<{a: 1; readonly b: {c?: 1}}, 'b.c'>; -expectType<{a: 1; readonly b: {c: 1}}>(variation12); +declare const variation11: SetRequiredDeep<{readonly a?: 1; readonly b?: {readonly c?: 1}}, 'a' | 'b'>; +expectType<{readonly a: 1; readonly b: {readonly c?: 1}}>(variation11); + +declare const variation12: SetRequiredDeep<{readonly a?: 1; readonly b?: {readonly c?: 1}}, 'a' | 'b' | 'b.c'>; +expectType<{readonly a: 1; readonly b: {readonly c: 1}}>(variation12); // Works with number keys declare const variation13: SetRequiredDeep<{0: 1; 1: {2?: string}}, '1.2'>; expectType<{0: 1; 1: {2: string}}>(variation13); +declare const variation14: SetRequiredDeep<{0?: 1; 1?: {2?: string}}, 0 | 1>; +expectType<{0: 1; 1: {2?: string}}>(variation14); + // Multiple keys -declare const variation14: SetRequiredDeep<{a?: 1; b?: {c?: 2}; d?: {e?: {f?: 2}; g?: 3}}, 'a' | 'b' | 'b.c' | 'd.e.f' | 'd.g'>; -expectType<{a: 1; b: {c: 2}; d?: {e?: {f: 2}; g: 3}}>(variation14); +declare const variation15: SetRequiredDeep<{a?: 1; b?: {c?: 2}; d?: {e?: {f?: 2}; g?: 3}}, 'a' | 'b' | 'b.c' | 'd.e.f' | 'd.g'>; +expectType<{a: 1; b: {c: 2}; d?: {e?: {f: 2}; g: 3}}>(variation15); // Index signatures -declare const variation15: SetRequiredDeep<{[x: string]: any; a?: number; b?: {c?: number}}, 'a' | 'b.c'>; -expectType<{[x: string]: any; a: number; b?: {c: number}}>(variation15); +declare const variation16: SetRequiredDeep<{[x: string]: any; a?: number; b?: {c?: number}}, 'a' | 'b.c'>; +expectType<{[x: string]: any; a: number; b?: {c: number}}>(variation16); + +// Preserves union in nested keys +declare const variation17: SetRequiredDeep<{a: 1; b?: {c?: 1} | number}, 'b'>; +expectType<{a: 1; b: {c?: 1} | number}>(variation17); + +declare const variation18: SetRequiredDeep<{a?: number; b?: {c?: number} | {d?: string}}, 'b' | 'b.d'>; +expectType<{a?: number; b: {c?: number} | {d: string}}>(variation18); + +// Works with number keys containing dots +// NOTE: Passing "1.2" instead of 1.2 will treat it as a path instead of a key +declare const variation19: SetRequiredDeep<{1.2?: string; 1?: {2?: string}}, 1.2>; +expectType<{1.2: string; 1?: {2?: string}}>(variation19); + +declare const variation20: SetRequiredDeep<{1.2?: string; 1?: {2?: string}}, '1.2'>; +expectType<{1.2?: string; 1?: {2: string}}>(variation20); + +declare const variation21: SetRequiredDeep<{1.2?: string; 1?: {2?: string}}, 1.2 | '1.2'>; +expectType<{1.2: string; 1?: {2: string}}>(variation21); + +// Works with unions +declare const variation22: SetRequiredDeep<{a?: {readonly b?: number}} | {readonly b?: {c?: number[]}}, 'a.b' | 'b' | 'b.c'>; +expectType<{a?: {readonly b: number}} | {readonly b: {c: number[]}}>(variation22); + +// Works with `KeyPaths` containing template literals +declare const variation23: SetRequiredDeep<{a?: number; b?: {c?: number} | {d?: number}}, `b.${'c' | 'd'}`>; +expectType<{a?: number; b?: {c: number} | {d: number}}>(variation23); + +declare const variation24: SetRequiredDeep< +{a?: number; b?: {readonly c?: {1?: number}} | {d?: {1?: number}}}, 'a' | `b.${'c' | 'd'}.1` +>; +expectType<{a: number; b?: {readonly c?: {1: number}} | {d?: {1: number}}}>(variation24); + +// Calls `RequiredDeep` when `KeyPaths` is `any` +declare const variation25: SetRequiredDeep<{a?: number; readonly b?: {c?: string}}, any>; +expectType<{a: number; readonly b: {c: string}}>(variation25); + +// Does nothing when `KeyPaths` is `never` +declare const variation26: SetRequiredDeep<{a?: number; readonly b?: {c?: string}}, never>; +expectType<{a?: number; readonly b?: {c?: string}}>(variation26); + +// ================= +// Works with arrays +// ================= + +// All optional elements +expectType<{a?: [string, number, boolean?]}>({} as SetRequiredDeep<{a?: [string?, number?, boolean?]}, 'a.0' | 'a.1'>); + +// Mix of optional and required elements +expectType<{a: readonly [string, number, boolean]}>({} as SetRequiredDeep<{a: readonly [string, number?, boolean?]}, 'a.1' | 'a.2'>); + +// Mix of optional and rest elements +expectType<{readonly a: [string, number, boolean?, ...number[]]}>({} as SetRequiredDeep<{readonly a: [string?, number?, boolean?, ...number[]]}, 'a.0' | 'a.1'>); + +// Mix of optional, required, and rest elements +expectType<{readonly a?: [string, number, boolean, ...string[]]}>({} as SetRequiredDeep<{readonly a?: [string, number?, boolean?, ...string[]]}, 'a.1' | 'a.2'>); + +// Works with readonly arrays +expectType<{a?: {b?: readonly [(string | number)]}}>({} as SetRequiredDeep<{a?: {b?: readonly [(string | number)?]}}, 'a.b.0'>); +expectType<{a: readonly [string, number, boolean, ...string[]]}>( + {} as SetRequiredDeep<{a?: readonly [string, number?, boolean?, ...string[]]}, 'a' | 'a.1' | 'a.2'>, +); + +// Ignores `Keys` that are already required +expectType<{a: [string, number?, boolean?]}>({} as SetRequiredDeep<{a: [string, number?, boolean?]}, 'a.0'>); + +// Ignores `Keys` that are not known +// This case is only possible when the array contains a rest element, +// because otherwise the constaint on `KeyPaths` would disallow out of bound keys. +expectType<{a?: readonly [string?, number?, boolean?, ...number[]]}>( + {} as SetRequiredDeep<{a?: readonly [string?, number?, boolean?, ...number[]]}, 'a.10'>, +); + +// Marks all keys as required, if `Keys` is `number`. +// This case is only possible when the array contains a rest element, +// because otherwise the constaint on `KeyPaths` would be stricter. +expectType<{a?: readonly [string, number, boolean, ...number[]]}>( + {} as SetRequiredDeep<{a?: readonly [string?, number?, boolean?, ...number[]]}, `a.${number}`>, +); + +// Preserves `| undefined`, similar to how built-in `Required` works. +expectType<{a: [string | undefined, number | undefined, boolean]}>({} as SetRequiredDeep<{a: [string | undefined, (number | undefined)?, boolean?]}, 'a.0' | 'a.1' | 'a.2'>); +expectType<{a: readonly [string | undefined, (number | undefined)?, boolean?]}>( + {} as SetRequiredDeep<{a: readonly [(string | undefined)?, (number | undefined)?, boolean?]}, 'a.0'>, +); + +// Optional elements cannot appear after required ones, `Keys` leading to such situations are ignored. +expectType<{a: [string?, number?, boolean?]}>({} as SetRequiredDeep<{a: [string?, number?, boolean?]}, 'a.1' | 'a.2'>); // `a.1` and `a.2` can't be required when `a.0` is optional +expectType<{a: [string, number, boolean?, string?, string?]}>( + {} as SetRequiredDeep<{a: [string?, number?, boolean?, string?, string?]}, 'a.0' | 'a.1' | 'a.3'>, // `a.3` can't be required when `a.2` is optional +); +expectType<{a: readonly [string | undefined, number?, boolean?, ...string[]]}>( + {} as SetRequiredDeep<{a?: readonly [string | undefined, number?, boolean?, ...string[]]}, 'a' | 'a.2'>, // `a.2` can't be required when `a.1` is optional +); + +// Works with unions of arrays +expectType<{a: [string] | [string, number, boolean?, ...number[]] | readonly [string, number, boolean?]}>( + {} as SetRequiredDeep<{a: [string?] | [string, number?, boolean?, ...number[]] | readonly [string, number?, boolean?]}, 'a.0' | 'a.1'>, +); + +// Works with labelled tuples +expectType<{a?: [b: string, c: number]}>({} as SetRequiredDeep<{a?: [b?: string, c?: number]}, 'a.0' | 'a.1'>); + +// Non tuple arrays are left unchanged +expectType<{a: string[]}>({} as SetRequiredDeep<{a: string[]}, `a.${number}`>); +expectType<{readonly a: ReadonlyArray}>({} as SetRequiredDeep<{readonly a?: ReadonlyArray}, 'a' | `a.${number}`>); + +// Works with nested arrays +expectType<{a?: [[string, number?]?]}>({} as SetRequiredDeep<{a?: [[string?, number?]?]}, 'a.0.0'>); +expectType<{a?: [[string, number]]}>({} as SetRequiredDeep<{a?: [[string?, number?]?]}, 'a.0' | 'a.0.0' | 'a.0.1'>); +expectType<{a?: Array<[string, number?]>}>({} as SetRequiredDeep<{a?: Array<[string?, number?]>}, `a.${number}.0`>); + +// Set key inside array to required +expectType<{a?: Array<{b: number}>}>({} as SetRequiredDeep<{a?: Array<{b?: number}>}, `a.${number}.b`>); +expectType<{readonly a?: [{readonly b: number}]}>({} as SetRequiredDeep<{readonly a?: [{readonly b?: number}]}, 'a.0.b'>); +expectType<{readonly a: [{readonly b: number}, {c?: string}]}>( + {} as SetRequiredDeep<{readonly a?: [{readonly b?: number}, {c?: string}?]}, 'a' | 'a.0.b' | 'a.1' >, +); + +// Set only specified keys inside array to required +expectType<{a?: Array<{b: number; c?: string}>}>({} as SetRequiredDeep<{a?: Array<{b?: number; c?: string}>}, `a.${number}.b`>); +expectType<{a: [{b?: number; readonly c: string}]}>({} as SetRequiredDeep<{a: [{b?: number; readonly c?: string}]}, 'a.0.c'>);