Refactor how options are handled for types (#1081)

This commit is contained in:
Som Shekhar Mukherjee 2025-03-18 21:52:18 +05:30 committed by GitHub
parent ed8c987129
commit db76e5b15f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 344 additions and 100 deletions

View File

@ -1,3 +1,4 @@
import type {ApplyDefaultOptions} from './internal';
import type {Words} from './words';
/**
@ -14,12 +15,16 @@ export type CamelCaseOptions = {
preserveConsecutiveUppercase?: boolean;
};
export type DefaultCamelCaseOptions = {
preserveConsecutiveUppercase: true;
};
/**
Convert an array of words to camel-case.
*/
type CamelCaseFromArray<
Words extends string[],
Options extends CamelCaseOptions,
Options extends Required<CamelCaseOptions>,
OutputString extends string = '',
> = Words extends [
infer FirstWord extends string,
@ -73,8 +78,11 @@ const dbResult: CamelCasedProperties<RawOptions> = {
@category Change case
@category Template literal
*/
export type CamelCase<Type, Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}> = Type extends string
export type CamelCase<Type, Options extends CamelCaseOptions = {}> = Type extends string
? string extends Type
? Type
: Uncapitalize<CamelCaseFromArray<Words<Type extends Uppercase<Type> ? Lowercase<Type> : Type>, Options>>
: Uncapitalize<CamelCaseFromArray<
Words<Type extends Uppercase<Type> ? Lowercase<Type> : Type>,
ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>
>>
: Type;

View File

@ -1,5 +1,5 @@
import type {CamelCase, CamelCaseOptions} from './camel-case';
import type {NonRecursiveType} from './internal';
import type {CamelCase, CamelCaseOptions, DefaultCamelCaseOptions} from './camel-case';
import type {ApplyDefaultOptions, NonRecursiveType} from './internal';
import type {UnknownArray} from './unknown-array';
/**
@ -48,15 +48,20 @@ const result: CamelCasedPropertiesDeep<UserWithFriends> = {
*/
export type CamelCasedPropertiesDeep<
Value,
Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true},
Options extends CamelCaseOptions = {},
> = _CamelCasedPropertiesDeep<Value, ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>>;
type _CamelCasedPropertiesDeep<
Value,
Options extends Required<CamelCaseOptions>,
> = Value extends NonRecursiveType
? Value
: Value extends UnknownArray
? CamelCasedPropertiesArrayDeep<Value>
: Value extends Set<infer U>
? Set<CamelCasedPropertiesDeep<U, Options>>
? Set<_CamelCasedPropertiesDeep<U, Options>>
: {
[K in keyof Value as CamelCase<K, Options>]: CamelCasedPropertiesDeep<
[K in keyof Value as CamelCase<K, Options>]: _CamelCasedPropertiesDeep<
Value[K],
Options
>;

View File

@ -1,4 +1,5 @@
import type {CamelCase, CamelCaseOptions} from './camel-case';
import type {CamelCase, CamelCaseOptions, DefaultCamelCaseOptions} from './camel-case';
import type {ApplyDefaultOptions} from './internal';
/**
Convert object properties to camel case but not recursively.
@ -27,10 +28,12 @@ const result: CamelCasedProperties<User> = {
@category Template literal
@category Object
*/
export type CamelCasedProperties<Value, Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}> = Value extends Function
export type CamelCasedProperties<Value, Options extends CamelCaseOptions = {}> = Value extends Function
? Value
: Value extends Array<infer U>
? Value
: {
[K in keyof Value as CamelCase<K, Options>]: Value[K];
[K in keyof Value as
CamelCase<K, ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>>
]: Value[K];
};

View File

@ -3,7 +3,7 @@ import type {ConditionalExcept} from './conditional-except';
import type {ConditionalSimplifyDeep} from './conditional-simplify';
import type {UnknownRecord} from './unknown-record';
import type {EmptyObject} from './empty-object';
import type {IsPlainObject} from './internal';
import type {ApplyDefaultOptions, IsPlainObject} from './internal';
/**
Used to mark properties that should be excluded.
@ -33,6 +33,10 @@ export type ConditionalPickDeepOptions = {
condition?: 'extends' | 'equality';
};
type DefaultConditionalPickDeepOptions = {
condition: 'extends';
};
/**
Pick keys recursively from the shape that matches the given condition.
@ -95,10 +99,20 @@ export type ConditionalPickDeep<
Type,
Condition,
Options extends ConditionalPickDeepOptions = {},
> = _ConditionalPickDeep<
Type,
Condition,
ApplyDefaultOptions<ConditionalPickDeepOptions, DefaultConditionalPickDeepOptions, Options>
>;
type _ConditionalPickDeep<
Type,
Condition,
Options extends Required<ConditionalPickDeepOptions>,
> = ConditionalSimplifyDeep<ConditionalExcept<{
[Key in keyof Type]: AssertCondition<Type[Key], Condition, Options> extends true
? Type[Key]
: IsPlainObject<Type[Key]> extends true
? ConditionalPickDeep<Type[Key], Condition, Options>
? _ConditionalPickDeep<Type[Key], Condition, Options>
: typeof conditionalPickDeepSymbol;
}, (typeof conditionalPickDeepSymbol | undefined) | EmptyObject>, never, UnknownRecord>;

View File

@ -1,5 +1,9 @@
import type {ApplyDefaultOptions} from './internal';
import type {IsStringLiteral} from './is-literal';
import type {Words, WordsOptions} from './words';
import type {Merge} from './merge';
import type {DefaultWordsOptions, Words, WordsOptions} from './words';
export type DefaultDelimiterCaseOptions = Merge<DefaultWordsOptions, {splitOnNumbers: false}>;
/**
Convert an array of words to delimiter case starting with a delimiter with input capitalization.
@ -61,13 +65,12 @@ const rawCliOptions: OddlyCasedProperties<SomeOptions> = {
export type DelimiterCase<
Value,
Delimiter extends string,
Options extends WordsOptions = {splitOnNumbers: false},
Options extends WordsOptions = {},
> = Value extends string
? IsStringLiteral<Value> extends false
? Value
: Lowercase<
RemoveFirstLetter<
DelimiterCaseFromArray<Words<Value, Options>, Delimiter>
>
>
: Lowercase<RemoveFirstLetter<DelimiterCaseFromArray<
Words<Value, ApplyDefaultOptions<WordsOptions, DefaultDelimiterCaseOptions, Options>>,
Delimiter
>>>
: Value;

10
source/except.d.ts vendored
View File

@ -1,3 +1,4 @@
import type {ApplyDefaultOptions} from './internal';
import type {IsEqual} from './is-equal';
/**
@ -40,6 +41,10 @@ type ExceptOptions = {
requireExactProps?: boolean;
};
type DefaultExceptOptions = {
requireExactProps: false;
};
/**
Create a type from an object type without certain keys.
@ -93,7 +98,10 @@ type PostPayload = Except<UserData, 'email'>;
@category Object
*/
export type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {requireExactProps: false}> = {
export type Except<ObjectType, KeysType extends keyof ObjectType, Options extends ExceptOptions = {}> =
_Except<ObjectType, KeysType, ApplyDefaultOptions<ExceptOptions, DefaultExceptOptions, Options>>;
type _Except<ObjectType, KeysType extends keyof ObjectType, Options extends Required<ExceptOptions>> = {
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
} & (Options['requireExactProps'] extends true
? Partial<Record<KeysType, never>>

23
source/get.d.ts vendored
View File

@ -1,4 +1,4 @@
import type {StringDigit, ToString} from './internal';
import type {ApplyDefaultOptions, StringDigit, ToString} from './internal';
import type {LiteralStringUnion} from './literal-union';
import type {Paths} from './paths';
import type {Split} from './split';
@ -15,10 +15,14 @@ type GetOptions = {
strict?: boolean;
};
type DefaultGetOptions = {
strict: true;
};
/**
Like the `Get` type but receives an array of strings as a path parameter.
*/
type GetWithPath<BaseType, Keys, Options extends GetOptions = {}> =
type GetWithPath<BaseType, Keys, Options extends Required<GetOptions>> =
Keys extends readonly []
? BaseType
: Keys extends readonly [infer Head, ...infer Tail]
@ -32,7 +36,7 @@ type GetWithPath<BaseType, Keys, Options extends GetOptions = {}> =
/**
Adds `undefined` to `Type` if `strict` is enabled.
*/
type Strictify<Type, Options extends GetOptions> =
type Strictify<Type, Options extends Required<GetOptions>> =
Options['strict'] extends false ? Type : (Type | undefined);
/**
@ -41,7 +45,7 @@ If `Options['strict']` is `true`, includes `undefined` in the returned type when
Known limitations:
- Does not include `undefined` in the type on object types with an index signature (for example, `{a: string; [key: string]: string}`).
*/
type StrictPropertyOf<BaseType, Key extends keyof BaseType, Options extends GetOptions> =
type StrictPropertyOf<BaseType, Key extends keyof BaseType, Options extends Required<GetOptions>> =
Record<string, any> extends BaseType
? string extends keyof BaseType
? Strictify<BaseType[Key], Options> // Record<string, any>
@ -122,7 +126,7 @@ Note:
- Returns `unknown` if `Key` is not a property of `BaseType`, since TypeScript uses structural typing, and it cannot be guaranteed that extra properties unknown to the type system will exist at runtime.
- Returns `undefined` from nullish values, to match the behaviour of most deep-key libraries like `lodash`, `dot-prop`, etc.
*/
type PropertyOf<BaseType, Key extends string, Options extends GetOptions = {}> =
type PropertyOf<BaseType, Key extends string, Options extends Required<GetOptions>> =
BaseType extends null | undefined
? undefined
: Key extends keyof BaseType
@ -206,5 +210,10 @@ export type Get<
Path extends
| readonly string[]
| LiteralStringUnion<ToString<Paths<BaseType, {bracketNotation: false; maxRecursionDepth: 2}> | Paths<BaseType, {bracketNotation: true; maxRecursionDepth: 2}>>>,
Options extends GetOptions = {}> =
GetWithPath<BaseType, Path extends string ? ToPath<Path> : Path, Options>;
Options extends GetOptions = {},
> =
GetWithPath<
BaseType,
Path extends string ? ToPath<Path> : Path,
ApplyDefaultOptions<GetOptions, DefaultGetOptions, Options>
>;

View File

@ -2,6 +2,11 @@ import type {Simplify} from '../simplify';
import type {UnknownArray} from '../unknown-array';
import type {IsEqual} from '../is-equal';
import type {KeysOfUnion} from '../keys-of-union';
import type {RequiredKeysOf} from '../required-keys-of';
import type {Merge} from '../merge';
import type {IfAny} from '../if-any';
import type {IfNever} from '../if-never';
import type {OptionalKeysOf} from '../optional-keys-of';
import type {FilterDefinedKeys, FilterOptionalKeys} from './keys';
import type {NonRecursiveType} from './type';
import type {ToString} from './string';
@ -159,3 +164,69 @@ type ReadonlyKeys = ReadonlyKeysOfUnion<User | Post>;
export type ReadonlyKeysOfUnion<Union> = Union extends unknown ? keyof {
[Key in keyof Union as IsEqual<{[K in Key]: Union[Key]}, {readonly [K in Key]: Union[Key]}> extends true ? Key : never]: never
} : never;
/**
Merges user specified options with default options.
@example
```
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: false};
type SpecifiedOptions = {leavesOnly: true};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
//=> {maxRecursionDepth: 10; leavesOnly: true}
```
@example
```
// Complains if default values are not provided for optional options
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10};
type SpecifiedOptions = {};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~~~~
// Property 'leavesOnly' is missing in type 'DefaultPathsOptions' but required in type '{ maxRecursionDepth: number; leavesOnly: boolean; }'.
```
@example
```
// Complains if an option's default type does not conform to the expected type
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: 'no'};
type SpecifiedOptions = {};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~~~~
// Types of property 'leavesOnly' are incompatible. Type 'string' is not assignable to type 'boolean'.
```
@example
```
// Complains if an option's specified type does not conform to the expected type
type PathsOptions = {maxRecursionDepth?: number; leavesOnly?: boolean};
type DefaultPathsOptions = {maxRecursionDepth: 10; leavesOnly: false};
type SpecifiedOptions = {leavesOnly: 'yes'};
type Result = ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, SpecifiedOptions>;
// ~~~~~~~~~~~~~~~~
// Types of property 'leavesOnly' are incompatible. Type 'string' is not assignable to type 'boolean'.
```
*/
export type ApplyDefaultOptions<
Options extends object,
Defaults extends Simplify<Omit<Required<Options>, RequiredKeysOf<Options>> & Partial<Record<RequiredKeysOf<Options>, never>>>,
SpecifiedOptions extends Options,
> =
IfAny<SpecifiedOptions, Defaults,
IfNever<SpecifiedOptions, Defaults,
Simplify<Merge<Defaults, {
[Key in keyof SpecifiedOptions
as Key extends OptionalKeysOf<Options> ? undefined extends SpecifiedOptions[Key] ? never : Key : Key
]: SpecifiedOptions[Key]
}> & Required<Options>> // `& Required<Options>` ensures that `ApplyDefaultOptions<SomeOption, ...>` is always assignable to `Required<SomeOption>`
>>;

13
source/is-tuple.d.ts vendored
View File

@ -1,5 +1,6 @@
import type {IfAny} from './if-any';
import type {IfNever} from './if-never';
import type {ApplyDefaultOptions} from './internal';
import type {UnknownArray} from './unknown-array';
/**
@ -28,6 +29,10 @@ export type IsTupleOptions = {
fixedLengthOnly?: boolean;
};
type DefaultIsTupleOptions = {
fixedLengthOnly: true;
};
/**
Returns a boolean for whether the given array is a tuple.
@ -63,7 +68,13 @@ type RestItemsAllowed = IsTuple<[1, 2, ...number[]], {fixedLengthOnly: false}>;
*/
export type IsTuple<
TArray extends UnknownArray,
Options extends IsTupleOptions = {fixedLengthOnly: true},
Options extends IsTupleOptions = {},
> =
_IsTuple<TArray, ApplyDefaultOptions<IsTupleOptions, DefaultIsTupleOptions, Options>>;
type _IsTuple<
TArray extends UnknownArray,
Options extends Required<IsTupleOptions>,
> =
IfAny<TArray, boolean, IfNever<TArray, false,
TArray extends unknown // For distributing `TArray`

View File

@ -1,4 +1,5 @@
import type {DelimiterCase} from './delimiter-case';
import type {DefaultDelimiterCaseOptions, DelimiterCase} from './delimiter-case';
import type {ApplyDefaultOptions} from './internal';
import type {WordsOptions} from './words';
/**
@ -39,5 +40,5 @@ const rawCliOptions: KebabCasedProperties<CliOptions> = {
*/
export type KebabCase<
Value,
Options extends WordsOptions = {splitOnNumbers: false},
> = DelimiterCase<Value, '-', Options>;
Options extends WordsOptions = {},
> = DelimiterCase<Value, '-', ApplyDefaultOptions<WordsOptions, DefaultDelimiterCaseOptions, Options>>;

View File

@ -1,4 +1,4 @@
import type {BuiltIns} from './internal';
import type {ApplyDefaultOptions, BuiltIns} from './internal';
/**
@see {@link PartialDeep}
@ -38,6 +38,11 @@ export type PartialDeepOptions = {
readonly allowUndefinedInNonTupleArrays?: boolean;
};
type DefaultPartialDeepOptions = {
recurseIntoArrays: false;
allowUndefinedInNonTupleArrays: true;
};
/**
Create a type from another type with all keys and nested keys set to optional.
@ -87,7 +92,10 @@ const partialSettings: PartialDeep<Settings, {recurseIntoArrays: true}> = {
@category Set
@category Map
*/
export type PartialDeep<T, Options extends PartialDeepOptions = {}> = T extends BuiltIns | (((...arguments_: any[]) => unknown)) | (new (...arguments_: any[]) => unknown)
export type PartialDeep<T, Options extends PartialDeepOptions = {}> =
_PartialDeep<T, ApplyDefaultOptions<PartialDeepOptions, DefaultPartialDeepOptions, Options>>;
type _PartialDeep<T, Options extends Required<PartialDeepOptions>> = T extends BuiltIns | (((...arguments_: any[]) => unknown)) | (new (...arguments_: any[]) => unknown)
? T
: T extends Map<infer KeyType, infer ValueType>
? PartialMapDeep<KeyType, ValueType, Options>
@ -102,8 +110,8 @@ export type PartialDeep<T, Options extends PartialDeepOptions = {}> = T extends
? Options['recurseIntoArrays'] extends true
? ItemType[] extends T // Test for arrays (non-tuples) specifically
? readonly ItemType[] extends T // Differentiate readonly and mutable arrays
? ReadonlyArray<PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
: Array<PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
? ReadonlyArray<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
: Array<_PartialDeep<Options['allowUndefinedInNonTupleArrays'] extends false ? ItemType : ItemType | undefined, Options>>
: PartialObjectDeep<T, Options> // Tuples behave properly
: T // If they don't opt into array testing, just use the original type
: PartialObjectDeep<T, Options>
@ -112,26 +120,26 @@ export type PartialDeep<T, Options extends PartialDeepOptions = {}> = T extends
/**
Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`.
*/
type PartialMapDeep<KeyType, ValueType, Options extends PartialDeepOptions> = {} & Map<PartialDeep<KeyType, Options>, PartialDeep<ValueType, Options>>;
type PartialMapDeep<KeyType, ValueType, Options extends Required<PartialDeepOptions>> = {} & Map<_PartialDeep<KeyType, Options>, _PartialDeep<ValueType, Options>>;
/**
Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialSetDeep<T, Options extends PartialDeepOptions> = {} & Set<PartialDeep<T, Options>>;
type PartialSetDeep<T, Options extends Required<PartialDeepOptions>> = {} & Set<_PartialDeep<T, Options>>;
/**
Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialReadonlyMapDeep<KeyType, ValueType, Options extends PartialDeepOptions> = {} & ReadonlyMap<PartialDeep<KeyType, Options>, PartialDeep<ValueType, Options>>;
type PartialReadonlyMapDeep<KeyType, ValueType, Options extends Required<PartialDeepOptions>> = {} & ReadonlyMap<_PartialDeep<KeyType, Options>, _PartialDeep<ValueType, Options>>;
/**
Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialReadonlySetDeep<T, Options extends PartialDeepOptions> = {} & ReadonlySet<PartialDeep<T, Options>>;
type PartialReadonlySetDeep<T, Options extends Required<PartialDeepOptions>> = {} & ReadonlySet<_PartialDeep<T, Options>>;
/**
Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`.
*/
type PartialObjectDeep<ObjectType extends object, Options extends PartialDeepOptions> = {
[KeyType in keyof ObjectType]?: PartialDeep<ObjectType[KeyType], Options>
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = {
[KeyType in keyof ObjectType]?: _PartialDeep<ObjectType[KeyType], Options>
};

View File

@ -1,5 +1,5 @@
import type {IfUnknown} from './if-unknown';
import type {BuiltIns, LiteralKeyOf} from './internal';
import type {ApplyDefaultOptions, BuiltIns, LiteralKeyOf} from './internal';
import type {Merge} from './merge';
/**
@ -14,6 +14,10 @@ export type PartialOnUndefinedDeepOptions = {
readonly recurseIntoArrays?: boolean;
};
type DefaultPartialOnUndefinedDeepOptions = {
recurseIntoArrays: false;
};
/**
Create a deep version of another type where all keys accepting `undefined` type are set to optional.
@ -47,7 +51,10 @@ const testSettings: PartialOnUndefinedDeep<Settings> = {
@category Object
*/
export type PartialOnUndefinedDeep<T, Options extends PartialOnUndefinedDeepOptions = {}> = T extends Record<any, any> | undefined
export type PartialOnUndefinedDeep<T, Options extends PartialOnUndefinedDeepOptions = {}> =
_PartialOnUndefinedDeep<T, ApplyDefaultOptions<PartialOnUndefinedDeepOptions, DefaultPartialOnUndefinedDeepOptions, Options>>;
type _PartialOnUndefinedDeep<T, Options extends Required<PartialOnUndefinedDeepOptions>> = T extends Record<any, any> | undefined
? {[KeyType in keyof T as undefined extends T[KeyType] ? IfUnknown<T[KeyType], never, KeyType> : never]?: PartialOnUndefinedDeepValue<T[KeyType], Options>} extends infer U // Make a partial type with all value types accepting undefined (and set them optional)
? Merge<{[KeyType in keyof T as KeyType extends LiteralKeyOf<U> ? never : KeyType]: PartialOnUndefinedDeepValue<T[KeyType], Options>}, U> // Join all remaining keys not treated in U
: never // Should not happen
@ -56,16 +63,16 @@ export type PartialOnUndefinedDeep<T, Options extends PartialOnUndefinedDeepOpti
/**
Utility type to get the value type by key and recursively call `PartialOnUndefinedDeep` to transform sub-objects.
*/
type PartialOnUndefinedDeepValue<T, Options extends PartialOnUndefinedDeepOptions> = T extends BuiltIns | ((...arguments_: any[]) => unknown)
type PartialOnUndefinedDeepValue<T, Options extends Required<PartialOnUndefinedDeepOptions>> = T extends BuiltIns | ((...arguments_: any[]) => unknown)
? T
: T extends ReadonlyArray<infer U> // Test if type is array or tuple
? Options['recurseIntoArrays'] extends true // Check if option is activated
? U[] extends T // Check if array not tuple
? readonly U[] extends T
? ReadonlyArray<PartialOnUndefinedDeep<U, Options>> // Readonly array treatment
: Array<PartialOnUndefinedDeep<U, Options>> // Mutable array treatment
: PartialOnUndefinedDeep<{[Key in keyof T]: PartialOnUndefinedDeep<T[Key], Options>}, Options> // Tuple treatment
? ReadonlyArray<_PartialOnUndefinedDeep<U, Options>> // Readonly array treatment
: Array<_PartialOnUndefinedDeep<U, Options>> // Mutable array treatment
: _PartialOnUndefinedDeep<{[Key in keyof T]: _PartialOnUndefinedDeep<T[Key], Options>}, Options> // Tuple treatment
: T
: T extends Record<any, any> | undefined
? PartialOnUndefinedDeep<T, Options>
? _PartialOnUndefinedDeep<T, Options>
: unknown;

View File

@ -1,4 +1,5 @@
import type {CamelCase, CamelCaseOptions} from './camel-case';
import type {CamelCase, CamelCaseOptions, DefaultCamelCaseOptions} from './camel-case';
import type {ApplyDefaultOptions} from './internal';
/**
Converts a string literal to pascal-case.
@ -33,6 +34,9 @@ const dbResult: CamelCasedProperties<ModelProps> = {
@category Change case
@category Template literal
*/
export type PascalCase<Value, Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}> = CamelCase<Value, Options> extends string
export type PascalCase<Value, Options extends CamelCaseOptions = {}> =
_PascalCase<Value, ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>>;
type _PascalCase<Value, Options extends Required<CamelCaseOptions>> = CamelCase<Value, Options> extends string
? Capitalize<CamelCase<Value, Options>>
: CamelCase<Value, Options>;

View File

@ -1,4 +1,5 @@
import type {CamelCaseOptions} from './camel-case';
import type {CamelCaseOptions, DefaultCamelCaseOptions} from './camel-case';
import type {ApplyDefaultOptions} from './internal';
import type {PascalCase} from './pascal-case';
/**
@ -45,11 +46,14 @@ const result: PascalCasedPropertiesDeep<UserWithFriends> = {
@category Template literal
@category Object
*/
export type PascalCasedPropertiesDeep<Value, Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}> = Value extends Function | Date | RegExp
export type PascalCasedPropertiesDeep<Value, Options extends CamelCaseOptions = {}> =
_PascalCasedPropertiesDeep<Value, ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>>;
type _PascalCasedPropertiesDeep<Value, Options extends Required<CamelCaseOptions>> = Value extends Function | Date | RegExp
? Value
: Value extends Array<infer U>
? Array<PascalCasedPropertiesDeep<U, Options>>
? Array<_PascalCasedPropertiesDeep<U, Options>>
: Value extends Set<infer U>
? Set<PascalCasedPropertiesDeep<U, Options>> : {
[K in keyof Value as PascalCase<K, Options>]: PascalCasedPropertiesDeep<Value[K], Options>;
? Set<_PascalCasedPropertiesDeep<U, Options>> : {
[K in keyof Value as PascalCase<K, Options>]: _PascalCasedPropertiesDeep<Value[K], Options>;
};

View File

@ -1,4 +1,5 @@
import type {CamelCaseOptions} from './camel-case';
import type {CamelCaseOptions, DefaultCamelCaseOptions} from './camel-case';
import type {ApplyDefaultOptions} from './internal';
import type {PascalCase} from './pascal-case';
/**
@ -28,8 +29,8 @@ const result: PascalCasedProperties<User> = {
@category Template literal
@category Object
*/
export type PascalCasedProperties<Value, Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}> = Value extends Function
export type PascalCasedProperties<Value, Options extends CamelCaseOptions = {}> = Value extends Function
? Value
: Value extends Array<infer U>
? Value
: {[K in keyof Value as PascalCase<K, Options>]: Value[K]};
: {[K in keyof Value as PascalCase<K, ApplyDefaultOptions<CamelCaseOptions, DefaultCamelCaseOptions, Options>>]: Value[K]};

13
source/paths.d.ts vendored
View File

@ -1,4 +1,4 @@
import type {StaticPartOfArray, VariablePartOfArray, NonRecursiveType, ToString, IsNumberLike} from './internal';
import type {StaticPartOfArray, VariablePartOfArray, NonRecursiveType, ToString, IsNumberLike, ApplyDefaultOptions} from './internal';
import type {EmptyObject} from './empty-object';
import type {IsAny} from './is-any';
import type {UnknownArray} from './unknown-array';
@ -176,16 +176,7 @@ open('listB.1'); // TypeError. Because listB only has one element.
@category Object
@category Array
*/
export type Paths<T, Options extends PathsOptions = {}> = _Paths<T, {
// Set default maxRecursionDepth to 10
maxRecursionDepth: Options['maxRecursionDepth'] extends number ? Options['maxRecursionDepth'] : DefaultPathsOptions['maxRecursionDepth'];
// Set default bracketNotation to false
bracketNotation: Options['bracketNotation'] extends boolean ? Options['bracketNotation'] : DefaultPathsOptions['bracketNotation'];
// Set default leavesOnly to false
leavesOnly: Options['leavesOnly'] extends boolean ? Options['leavesOnly'] : DefaultPathsOptions['leavesOnly'];
// Set default depth to number
depth: Options['depth'] extends number ? Options['depth'] : DefaultPathsOptions['depth'];
}>;
export type Paths<T, Options extends PathsOptions = {}> = _Paths<T, ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, Options>>;
type _Paths<T, Options extends Required<PathsOptions>> =
T extends NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>

10
source/replace.d.ts vendored
View File

@ -1,7 +1,13 @@
import type {ApplyDefaultOptions} from './internal';
type ReplaceOptions = {
all?: boolean;
};
type DefaultReplaceOptions = {
all: false;
};
/**
Represents a string with some or all matches replaced by a replacement.
@ -60,13 +66,13 @@ export type Replace<
Search extends string,
Replacement extends string,
Options extends ReplaceOptions = {},
> = _Replace<Input, Search, Replacement, Options>;
> = _Replace<Input, Search, Replacement, ApplyDefaultOptions<ReplaceOptions, DefaultReplaceOptions, Options>>;
type _Replace<
Input extends string,
Search extends string,
Replacement extends string,
Options extends ReplaceOptions,
Options extends Required<ReplaceOptions>,
Accumulator extends string = '',
> = Search extends string // For distributing `Search`
? Replacement extends string // For distributing `Replacement`

View File

@ -1,3 +1,5 @@
import type {DefaultDelimiterCaseOptions} from './delimiter-case';
import type {ApplyDefaultOptions} from './internal';
import type {SnakeCase} from './snake-case';
import type {WordsOptions} from './words';
@ -20,5 +22,7 @@ const someVariableNoSplitOnNumbers: ScreamingSnakeCase<'p2pNetwork', {splitOnNum
*/
export type ScreamingSnakeCase<
Value,
Options extends WordsOptions = {splitOnNumbers: false},
> = Value extends string ? Uppercase<SnakeCase<Value, Options>> : Value;
Options extends WordsOptions = {},
> = Value extends string
? Uppercase<SnakeCase<Value, ApplyDefaultOptions<WordsOptions, DefaultDelimiterCaseOptions, Options>>>
: Value;

View File

@ -1,3 +1,4 @@
import type {ApplyDefaultOptions} from './internal';
import type {Simplify} from './simplify';
type SetFieldTypeOptions = {
@ -11,6 +12,10 @@ type SetFieldTypeOptions = {
preservePropertyModifiers?: boolean;
};
type DefaultSetFieldTypeOptions = {
preservePropertyModifiers: true;
};
/**
Create a type that changes the type of the given keys.
@ -48,7 +53,10 @@ type MyModelApi = SetFieldType<MyModel, 'createdAt' | 'updatedAt', string, {pres
@category Object
*/
export type SetFieldType<BaseType, Keys extends keyof BaseType, NewType, Options extends SetFieldTypeOptions = {preservePropertyModifiers: true}> =
export type SetFieldType<BaseType, Keys extends keyof BaseType, NewType, Options extends SetFieldTypeOptions = {}> =
_SetFieldType<BaseType, Keys, NewType, ApplyDefaultOptions<SetFieldTypeOptions, DefaultSetFieldTypeOptions, Options>>;
type _SetFieldType<BaseType, Keys extends keyof BaseType, NewType, Options extends Required<SetFieldTypeOptions>> =
Simplify<{
[P in keyof BaseType]: P extends Keys ? NewType : BaseType[P];
} & (

View File

@ -1,4 +1,4 @@
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion, IsArrayReadonly, SetArrayAccess} from './internal';
import type {NonRecursiveType, UnionMin, UnionMax, TupleLength, StaticPartOfArray, VariablePartOfArray, IsUnion, IsArrayReadonly, SetArrayAccess, ApplyDefaultOptions} from './internal';
import type {IsNever} from './is-never';
import type {UnknownArray} from './unknown-array';
@ -16,6 +16,10 @@ export type SharedUnionFieldsDeepOptions = {
recurseIntoArrays?: boolean;
};
type DefaultSharedUnionFieldsDeepOptions = {
recurseIntoArrays: false;
};
/**
Create a type with shared fields from a union of object types, deeply traversing nested structures.
@ -82,24 +86,26 @@ function displayPetInfo(petInfo: SharedUnionFieldsDeep<Cat | Dog>['info']) {
@category Object
@category Union
*/
export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {recurseIntoArrays: false}> =
export type SharedUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions = {}> =
ApplyDefaultOptions<SharedUnionFieldsDeepOptions, DefaultSharedUnionFieldsDeepOptions, Options> extends infer OptionsWithDefaults extends Required<SharedUnionFieldsDeepOptions>
// `Union extends` will convert `Union`
// to a [distributive conditionaltype](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
// But this is not what we want, so we need to wrap `Union` with `[]` to prevent it.
[Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>]
? Union
: [Union] extends [UnknownArray]
? Options['recurseIntoArrays'] extends true
? SetArrayAccess<SharedArrayUnionFieldsDeep<Union, Options>, IsArrayReadonly<Union>>
: Union
: [Union] extends [object]
? SharedObjectUnionFieldsDeep<Union, Options>
: Union;
? [Union] extends [NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>]
? Union
: [Union] extends [UnknownArray]
? OptionsWithDefaults['recurseIntoArrays'] extends true
? SetArrayAccess<SharedArrayUnionFieldsDeep<Union, OptionsWithDefaults>, IsArrayReadonly<Union>>
: Union
: [Union] extends [object]
? SharedObjectUnionFieldsDeep<Union, OptionsWithDefaults>
: Union
: never;
/**
Same as `SharedUnionFieldsDeep`, but accepts only `object`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
*/
type SharedObjectUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOptions> =
type SharedObjectUnionFieldsDeep<Union, Options extends Required<SharedUnionFieldsDeepOptions>> =
// `keyof Union` can extract the same key in union type, if there is no same key, return never.
keyof Union extends infer Keys
? IsNever<Keys> extends false
@ -119,7 +125,7 @@ type SharedObjectUnionFieldsDeep<Union, Options extends SharedUnionFieldsDeepOpt
/**
Same as `SharedUnionFieldsDeep`, but accepts only `UnknownArray`s and as inputs. Internal helper for `SharedUnionFieldsDeep`.
*/
type SharedArrayUnionFieldsDeep<Union extends UnknownArray, Options extends SharedUnionFieldsDeepOptions> =
type SharedArrayUnionFieldsDeep<Union extends UnknownArray, Options extends Required<SharedUnionFieldsDeepOptions>> =
// Restore the readonly modifier of the array.
SetArrayAccess<
InternalSharedArrayUnionFieldsDeep<Union, Options>,
@ -131,7 +137,7 @@ Internal helper for `SharedArrayUnionFieldsDeep`. Needn't care the `readonly` mo
*/
type InternalSharedArrayUnionFieldsDeep<
Union extends UnknownArray,
Options extends SharedUnionFieldsDeepOptions,
Options extends Required<SharedUnionFieldsDeepOptions>,
ResultTuple extends UnknownArray = [],
> =
// We should build a minimum possible length tuple where each element in the tuple exists in the union tuple.

View File

@ -1,4 +1,5 @@
import type {DelimiterCase} from './delimiter-case';
import type {DefaultDelimiterCaseOptions, DelimiterCase} from './delimiter-case';
import type {ApplyDefaultOptions} from './internal';
import type {WordsOptions} from './words';
/**
@ -39,5 +40,5 @@ const dbResult: SnakeCasedProperties<ModelProps> = {
*/
export type SnakeCase<
Value,
Options extends WordsOptions = {splitOnNumbers: false},
> = DelimiterCase<Value, '_', Options>;
Options extends WordsOptions = {},
> = DelimiterCase<Value, '_', ApplyDefaultOptions<WordsOptions, DefaultDelimiterCaseOptions, Options>>;

7
source/split.d.ts vendored
View File

@ -1,5 +1,5 @@
import type {And} from './and';
import type {Not} from './internal';
import type {ApplyDefaultOptions, Not} from './internal';
import type {IsStringLiteral} from './is-literal';
import type {Or} from './or';
@ -65,9 +65,8 @@ export type Split<
S extends string,
Delimiter extends string,
Options extends SplitOptions = {},
> = SplitHelper<S, Delimiter, {
strictLiteralChecks: Options['strictLiteralChecks'] extends boolean ? Options['strictLiteralChecks'] : DefaultSplitOptions['strictLiteralChecks'];
}>;
> =
SplitHelper<S, Delimiter, ApplyDefaultOptions<SplitOptions, DefaultSplitOptions, Options>>;
type SplitHelper<
S extends string,

8
source/words.d.ts vendored
View File

@ -1,4 +1,5 @@
import type {
ApplyDefaultOptions,
IsLowerCase,
IsNumeric,
IsUpperCase,
@ -37,7 +38,7 @@ export type WordsOptions = {
splitOnNumbers?: boolean;
};
type DefaultOptions = {
export type DefaultWordsOptions = {
splitOnNumbers: true;
};
@ -74,9 +75,8 @@ type Words5 = Words<'p2pNetwork', {splitOnNumbers: false}>;
@category Change case
@category Template literal
*/
export type Words<Sentence extends string, Options extends WordsOptions = {}> = WordsImplementation<Sentence, {
splitOnNumbers: Options['splitOnNumbers'] extends boolean ? Options['splitOnNumbers'] : DefaultOptions['splitOnNumbers'];
}>;
export type Words<Sentence extends string, Options extends WordsOptions = {}> =
WordsImplementation<Sentence, ApplyDefaultOptions<WordsOptions, DefaultWordsOptions, Options>>;
type WordsImplementation<
Sentence extends string,

View File

@ -0,0 +1,72 @@
import {expectType} from 'tsd';
import type {ApplyDefaultOptions} from '../../source/internal';
type PathsOptions = {
maxRecursionDepth?: number;
bracketNotation?: boolean;
leavesOnly?: boolean;
depth?: number;
};
type DefaultPathsOptions = {
maxRecursionDepth: 10;
bracketNotation: false;
leavesOnly: false;
depth: number;
};
declare const noOptionsSpecified: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, {}>;
expectType<DefaultPathsOptions>(noOptionsSpecified);
declare const someOptionsSpecified: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, {leavesOnly: true; depth: 2}>;
expectType<{maxRecursionDepth: 10; bracketNotation: false; leavesOnly: true; depth: 2}>(someOptionsSpecified);
declare const someOptionsSpecified2: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, {maxRecursionDepth: 5}>;
expectType<{maxRecursionDepth: 5; bracketNotation: false; leavesOnly: false; depth: number}>(someOptionsSpecified2);
declare const allOptionsSpecified: ApplyDefaultOptions<
PathsOptions, DefaultPathsOptions, {maxRecursionDepth: 5; bracketNotation: false; leavesOnly: false; depth: 1}
>;
expectType<{maxRecursionDepth: 5; bracketNotation: false; leavesOnly: false; depth: 1}>(allOptionsSpecified);
declare const requiredOptions: ApplyDefaultOptions<{fixedLengthOnly?: boolean; strict: boolean}, {fixedLengthOnly: false}, {strict: true}>;
expectType<{fixedLengthOnly: false; strict: true}>(requiredOptions);
// @ts-ignore
declare const undefinedsGetOverwritten: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, {maxRecursionDepth: undefined}>; // Possible when `exactOptionalPropertyTypes` is disabled
expectType<DefaultPathsOptions>(undefinedsGetOverwritten);
// Caveat: User specified `undefined` for optional properties with explicit undefined also gets overwritten
declare const undefinedsGetOverwritten2: ApplyDefaultOptions<{recurseIntoArrays?: boolean | undefined}, {recurseIntoArrays: true}, {recurseIntoArrays: undefined}>;
expectType<{recurseIntoArrays: true}>(undefinedsGetOverwritten2);
declare const undefinedAsValidValue: ApplyDefaultOptions<{recurseIntoArrays: boolean | undefined}, {}, {recurseIntoArrays: undefined}>;
expectType<{recurseIntoArrays: undefined}>(undefinedAsValidValue);
declare const optionalOptionsGetOverwritten: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, {maxRecursionDepth?: 5; bracketNotation?: true}>;
expectType<DefaultPathsOptions>(optionalOptionsGetOverwritten);
declare const neverAsOptionsGetOverwritten: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, never>;
expectType<DefaultPathsOptions>(neverAsOptionsGetOverwritten);
declare const anyAsOptionsGetOverwritten: ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, any>;
expectType<DefaultPathsOptions>(anyAsOptionsGetOverwritten);
// @ts-expect-error - `Defaults` should be compatible with `Options`
declare const defaultsShouldBeCompatible: ApplyDefaultOptions<{fixedLengthOnly?: boolean}, {fixedLengthOnly: 'no'}, {}>;
// @ts-expect-error - `SpecifiedOptions` should be compatible with `Options`
declare const specifiedOptionsShouldBeCompatible: ApplyDefaultOptions<{fixedLengthOnly?: boolean}, {fixedLengthOnly: false}, {fixedLengthOnly: 'yes'}>;
// @ts-expect-error - Optional options should have a default value
declare const defaultForOptionalOptions: ApplyDefaultOptions<PathsOptions, Omit<DefaultPathsOptions, 'depth'>, {}>;
// @ts-expect-error - Required options should be specified
declare const requiredOptionsShouldBeSpecified: ApplyDefaultOptions<{fixedLengthOnly: boolean}, {}, {}>;
// @ts-expect-error - Required options should not have a default value
declare const noDefaultForRequiredOptions: ApplyDefaultOptions<{fixedLengthOnly: boolean}, {fixedLengthOnly: false}, {fixedLengthOnly: false}>;
// The output of `ApplyDefaultOptions<SomeOption, ...>` should be assignable to `Required<SomeOption>`
type SomeType<Options extends PathsOptions = {}> = _SomeType<ApplyDefaultOptions<PathsOptions, DefaultPathsOptions, Options>>;
type _SomeType<Options extends Required<PathsOptions>> = Options;