diff --git a/source/camel-case.d.ts b/source/camel-case.d.ts index 09ced5a9..db120b3e 100644 --- a/source/camel-case.d.ts +++ b/source/camel-case.d.ts @@ -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, OutputString extends string = '', > = Words extends [ infer FirstWord extends string, @@ -73,8 +78,11 @@ const dbResult: CamelCasedProperties = { @category Change case @category Template literal */ -export type CamelCase = Type extends string +export type CamelCase = Type extends string ? string extends Type ? Type - : Uncapitalize ? Lowercase : Type>, Options>> + : Uncapitalize ? Lowercase : Type>, + ApplyDefaultOptions + >> : Type; diff --git a/source/camel-cased-properties-deep.d.ts b/source/camel-cased-properties-deep.d.ts index 7dc34057..fbf7affb 100644 --- a/source/camel-cased-properties-deep.d.ts +++ b/source/camel-cased-properties-deep.d.ts @@ -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 = { */ export type CamelCasedPropertiesDeep< Value, - Options extends CamelCaseOptions = {preserveConsecutiveUppercase: true}, + Options extends CamelCaseOptions = {}, +> = _CamelCasedPropertiesDeep>; + +type _CamelCasedPropertiesDeep< + Value, + Options extends Required, > = Value extends NonRecursiveType ? Value : Value extends UnknownArray ? CamelCasedPropertiesArrayDeep : Value extends Set - ? Set> + ? Set<_CamelCasedPropertiesDeep> : { - [K in keyof Value as CamelCase]: CamelCasedPropertiesDeep< + [K in keyof Value as CamelCase]: _CamelCasedPropertiesDeep< Value[K], Options >; diff --git a/source/camel-cased-properties.d.ts b/source/camel-cased-properties.d.ts index 99e14819..7db7e90e 100644 --- a/source/camel-cased-properties.d.ts +++ b/source/camel-cased-properties.d.ts @@ -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 = { @category Template literal @category Object */ -export type CamelCasedProperties = Value extends Function +export type CamelCasedProperties = Value extends Function ? Value : Value extends Array ? Value : { - [K in keyof Value as CamelCase]: Value[K]; + [K in keyof Value as + CamelCase> + ]: Value[K]; }; diff --git a/source/conditional-pick-deep.d.ts b/source/conditional-pick-deep.d.ts index 2f84b3a1..561df238 100644 --- a/source/conditional-pick-deep.d.ts +++ b/source/conditional-pick-deep.d.ts @@ -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 +>; + +type _ConditionalPickDeep< + Type, + Condition, + Options extends Required, > = ConditionalSimplifyDeep extends true ? Type[Key] : IsPlainObject extends true - ? ConditionalPickDeep + ? _ConditionalPickDeep : typeof conditionalPickDeepSymbol; }, (typeof conditionalPickDeepSymbol | undefined) | EmptyObject>, never, UnknownRecord>; diff --git a/source/delimiter-case.d.ts b/source/delimiter-case.d.ts index 9181dc43..5a49fdcf 100644 --- a/source/delimiter-case.d.ts +++ b/source/delimiter-case.d.ts @@ -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; /** Convert an array of words to delimiter case starting with a delimiter with input capitalization. @@ -61,13 +65,12 @@ const rawCliOptions: OddlyCasedProperties = { export type DelimiterCase< Value, Delimiter extends string, - Options extends WordsOptions = {splitOnNumbers: false}, + Options extends WordsOptions = {}, > = Value extends string ? IsStringLiteral extends false ? Value - : Lowercase< - RemoveFirstLetter< - DelimiterCaseFromArray, Delimiter> - > - > + : Lowercase>, + Delimiter + >>> : Value; diff --git a/source/except.d.ts b/source/except.d.ts index 3aa97216..c4462908 100644 --- a/source/except.d.ts +++ b/source/except.d.ts @@ -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; @category Object */ -export type Except = { +export type Except = + _Except>; + +type _Except> = { [KeyType in keyof ObjectType as Filter]: ObjectType[KeyType]; } & (Options['requireExactProps'] extends true ? Partial> diff --git a/source/get.d.ts b/source/get.d.ts index c63c0b58..6bb3be1d 100644 --- a/source/get.d.ts +++ b/source/get.d.ts @@ -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 = +type GetWithPath> = Keys extends readonly [] ? BaseType : Keys extends readonly [infer Head, ...infer Tail] @@ -32,7 +36,7 @@ type GetWithPath = /** Adds `undefined` to `Type` if `strict` is enabled. */ -type Strictify = +type Strictify> = 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 = +type StrictPropertyOf> = Record extends BaseType ? string extends keyof BaseType ? Strictify // Record @@ -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 = +type PropertyOf> = BaseType extends null | undefined ? undefined : Key extends keyof BaseType @@ -206,5 +210,10 @@ export type Get< Path extends | readonly string[] | LiteralStringUnion | Paths>>, - Options extends GetOptions = {}> = - GetWithPath : Path, Options>; + Options extends GetOptions = {}, +> = + GetWithPath< + BaseType, + Path extends string ? ToPath : Path, + ApplyDefaultOptions + >; diff --git a/source/internal/object.d.ts b/source/internal/object.d.ts index 05857b50..ff42a217 100644 --- a/source/internal/object.d.ts +++ b/source/internal/object.d.ts @@ -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; export type ReadonlyKeysOfUnion = 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; +//=> {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; +// ~~~~~~~~~~~~~~~~~~~ +// 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; +// ~~~~~~~~~~~~~~~~~~~ +// 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; +// ~~~~~~~~~~~~~~~~ +// Types of property 'leavesOnly' are incompatible. Type 'string' is not assignable to type 'boolean'. +``` +*/ +export type ApplyDefaultOptions< + Options extends object, + Defaults extends Simplify, RequiredKeysOf> & Partial, never>>>, + SpecifiedOptions extends Options, +> = + IfAny ? undefined extends SpecifiedOptions[Key] ? never : Key : Key + ]: SpecifiedOptions[Key] + }> & Required> // `& Required` ensures that `ApplyDefaultOptions` is always assignable to `Required` + >>; diff --git a/source/is-tuple.d.ts b/source/is-tuple.d.ts index 4c829d6d..d2639c0c 100644 --- a/source/is-tuple.d.ts +++ b/source/is-tuple.d.ts @@ -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>; + +type _IsTuple< + TArray extends UnknownArray, + Options extends Required, > = IfAny = { */ export type KebabCase< Value, - Options extends WordsOptions = {splitOnNumbers: false}, -> = DelimiterCase; + Options extends WordsOptions = {}, +> = DelimiterCase>; diff --git a/source/partial-deep.d.ts b/source/partial-deep.d.ts index 8afdbbf5..82a896ea 100644 --- a/source/partial-deep.d.ts +++ b/source/partial-deep.d.ts @@ -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 = { @category Set @category Map */ -export type PartialDeep = T extends BuiltIns | (((...arguments_: any[]) => unknown)) | (new (...arguments_: any[]) => unknown) +export type PartialDeep = + _PartialDeep>; + +type _PartialDeep> = T extends BuiltIns | (((...arguments_: any[]) => unknown)) | (new (...arguments_: any[]) => unknown) ? T : T extends Map ? PartialMapDeep @@ -102,8 +110,8 @@ export type PartialDeep = 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> - : Array> + ? ReadonlyArray<_PartialDeep> + : Array<_PartialDeep> : PartialObjectDeep // Tuples behave properly : T // If they don't opt into array testing, just use the original type : PartialObjectDeep @@ -112,26 +120,26 @@ export type PartialDeep = T extends /** Same as `PartialDeep`, but accepts only `Map`s and as inputs. Internal helper for `PartialDeep`. */ -type PartialMapDeep = {} & Map, PartialDeep>; +type PartialMapDeep> = {} & Map<_PartialDeep, _PartialDeep>; /** Same as `PartialDeep`, but accepts only `Set`s as inputs. Internal helper for `PartialDeep`. */ -type PartialSetDeep = {} & Set>; +type PartialSetDeep> = {} & Set<_PartialDeep>; /** Same as `PartialDeep`, but accepts only `ReadonlyMap`s as inputs. Internal helper for `PartialDeep`. */ -type PartialReadonlyMapDeep = {} & ReadonlyMap, PartialDeep>; +type PartialReadonlyMapDeep> = {} & ReadonlyMap<_PartialDeep, _PartialDeep>; /** Same as `PartialDeep`, but accepts only `ReadonlySet`s as inputs. Internal helper for `PartialDeep`. */ -type PartialReadonlySetDeep = {} & ReadonlySet>; +type PartialReadonlySetDeep> = {} & ReadonlySet<_PartialDeep>; /** Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for `PartialDeep`. */ -type PartialObjectDeep = { - [KeyType in keyof ObjectType]?: PartialDeep +type PartialObjectDeep> = { + [KeyType in keyof ObjectType]?: _PartialDeep }; diff --git a/source/partial-on-undefined-deep.d.ts b/source/partial-on-undefined-deep.d.ts index b271ceee..5e8af6ec 100644 --- a/source/partial-on-undefined-deep.d.ts +++ b/source/partial-on-undefined-deep.d.ts @@ -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 = { @category Object */ -export type PartialOnUndefinedDeep = T extends Record | undefined +export type PartialOnUndefinedDeep = + _PartialOnUndefinedDeep>; + +type _PartialOnUndefinedDeep> = T extends Record | undefined ? {[KeyType in keyof T as undefined extends T[KeyType] ? IfUnknown : never]?: PartialOnUndefinedDeepValue} 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 ? never : KeyType]: PartialOnUndefinedDeepValue}, U> // Join all remaining keys not treated in U : never // Should not happen @@ -56,16 +63,16 @@ export type PartialOnUndefinedDeep = T extends BuiltIns | ((...arguments_: any[]) => unknown) +type PartialOnUndefinedDeepValue> = T extends BuiltIns | ((...arguments_: any[]) => unknown) ? T : T extends ReadonlyArray // 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> // Readonly array treatment - : Array> // Mutable array treatment - : PartialOnUndefinedDeep<{[Key in keyof T]: PartialOnUndefinedDeep}, Options> // Tuple treatment + ? ReadonlyArray<_PartialOnUndefinedDeep> // Readonly array treatment + : Array<_PartialOnUndefinedDeep> // Mutable array treatment + : _PartialOnUndefinedDeep<{[Key in keyof T]: _PartialOnUndefinedDeep}, Options> // Tuple treatment : T : T extends Record | undefined - ? PartialOnUndefinedDeep + ? _PartialOnUndefinedDeep : unknown; diff --git a/source/pascal-case.d.ts b/source/pascal-case.d.ts index 86782963..3e5590a0 100644 --- a/source/pascal-case.d.ts +++ b/source/pascal-case.d.ts @@ -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 = { @category Change case @category Template literal */ -export type PascalCase = CamelCase extends string +export type PascalCase = + _PascalCase>; + +type _PascalCase> = CamelCase extends string ? Capitalize> : CamelCase; diff --git a/source/pascal-cased-properties-deep.d.ts b/source/pascal-cased-properties-deep.d.ts index a8bbc603..8757a32e 100644 --- a/source/pascal-cased-properties-deep.d.ts +++ b/source/pascal-cased-properties-deep.d.ts @@ -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 = { @category Template literal @category Object */ -export type PascalCasedPropertiesDeep = Value extends Function | Date | RegExp +export type PascalCasedPropertiesDeep = + _PascalCasedPropertiesDeep>; + +type _PascalCasedPropertiesDeep> = Value extends Function | Date | RegExp ? Value : Value extends Array - ? Array> + ? Array<_PascalCasedPropertiesDeep> : Value extends Set - ? Set> : { - [K in keyof Value as PascalCase]: PascalCasedPropertiesDeep; + ? Set<_PascalCasedPropertiesDeep> : { + [K in keyof Value as PascalCase]: _PascalCasedPropertiesDeep; }; diff --git a/source/pascal-cased-properties.d.ts b/source/pascal-cased-properties.d.ts index b6d43303..5e69b0cf 100644 --- a/source/pascal-cased-properties.d.ts +++ b/source/pascal-cased-properties.d.ts @@ -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 = { @category Template literal @category Object */ -export type PascalCasedProperties = Value extends Function +export type PascalCasedProperties = Value extends Function ? Value : Value extends Array ? Value - : {[K in keyof Value as PascalCase]: Value[K]}; + : {[K in keyof Value as PascalCase>]: Value[K]}; diff --git a/source/paths.d.ts b/source/paths.d.ts index 55eb126c..1bba2c14 100644 --- a/source/paths.d.ts +++ b/source/paths.d.ts @@ -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 = _Paths; +export type Paths = _Paths>; type _Paths> = T extends NonRecursiveType | ReadonlyMap | ReadonlySet diff --git a/source/replace.d.ts b/source/replace.d.ts index b15903ba..e1abe086 100644 --- a/source/replace.d.ts +++ b/source/replace.d.ts @@ -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; +> = _Replace>; type _Replace< Input extends string, Search extends string, Replacement extends string, - Options extends ReplaceOptions, + Options extends Required, Accumulator extends string = '', > = Search extends string // For distributing `Search` ? Replacement extends string // For distributing `Replacement` diff --git a/source/screaming-snake-case.d.ts b/source/screaming-snake-case.d.ts index a813b796..27b3db81 100644 --- a/source/screaming-snake-case.d.ts +++ b/source/screaming-snake-case.d.ts @@ -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> : Value; + Options extends WordsOptions = {}, +> = Value extends string + ? Uppercase>> + : Value; diff --git a/source/set-field-type.d.ts b/source/set-field-type.d.ts index ecbe713e..58370027 100644 --- a/source/set-field-type.d.ts +++ b/source/set-field-type.d.ts @@ -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 = +export type SetFieldType = + _SetFieldType>; + +type _SetFieldType> = Simplify<{ [P in keyof BaseType]: P extends Keys ? NewType : BaseType[P]; } & ( diff --git a/source/shared-union-fields-deep.d.ts b/source/shared-union-fields-deep.d.ts index 937baa47..e385cf0d 100644 --- a/source/shared-union-fields-deep.d.ts +++ b/source/shared-union-fields-deep.d.ts @@ -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['info']) { @category Object @category Union */ -export type SharedUnionFieldsDeep = +export type SharedUnionFieldsDeep = + ApplyDefaultOptions extends infer OptionsWithDefaults extends Required // `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 | ReadonlySet] - ? Union - : [Union] extends [UnknownArray] - ? Options['recurseIntoArrays'] extends true - ? SetArrayAccess, IsArrayReadonly> - : Union - : [Union] extends [object] - ? SharedObjectUnionFieldsDeep - : Union; + ? [Union] extends [NonRecursiveType | ReadonlyMap | ReadonlySet] + ? Union + : [Union] extends [UnknownArray] + ? OptionsWithDefaults['recurseIntoArrays'] extends true + ? SetArrayAccess, IsArrayReadonly> + : Union + : [Union] extends [object] + ? SharedObjectUnionFieldsDeep + : Union + : never; /** Same as `SharedUnionFieldsDeep`, but accepts only `object`s and as inputs. Internal helper for `SharedUnionFieldsDeep`. */ -type SharedObjectUnionFieldsDeep = +type SharedObjectUnionFieldsDeep> = // `keyof Union` can extract the same key in union type, if there is no same key, return never. keyof Union extends infer Keys ? IsNever extends false @@ -119,7 +125,7 @@ type SharedObjectUnionFieldsDeep = +type SharedArrayUnionFieldsDeep> = // Restore the readonly modifier of the array. SetArrayAccess< InternalSharedArrayUnionFieldsDeep, @@ -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, ResultTuple extends UnknownArray = [], > = // We should build a minimum possible length tuple where each element in the tuple exists in the union tuple. diff --git a/source/snake-case.d.ts b/source/snake-case.d.ts index cb87b55b..b1555ff1 100644 --- a/source/snake-case.d.ts +++ b/source/snake-case.d.ts @@ -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 = { */ export type SnakeCase< Value, - Options extends WordsOptions = {splitOnNumbers: false}, -> = DelimiterCase; + Options extends WordsOptions = {}, +> = DelimiterCase>; diff --git a/source/split.d.ts b/source/split.d.ts index 871fb711..5abe2eeb 100644 --- a/source/split.d.ts +++ b/source/split.d.ts @@ -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; +> = + SplitHelper>; type SplitHelper< S extends string, diff --git a/source/words.d.ts b/source/words.d.ts index f27f6df5..37507f31 100644 --- a/source/words.d.ts +++ b/source/words.d.ts @@ -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 = WordsImplementation; +export type Words = + WordsImplementation>; type WordsImplementation< Sentence extends string, diff --git a/test-d/internal/apply-default-options.ts b/test-d/internal/apply-default-options.ts new file mode 100644 index 00000000..1889d93f --- /dev/null +++ b/test-d/internal/apply-default-options.ts @@ -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; +expectType(noOptionsSpecified); + +declare const someOptionsSpecified: ApplyDefaultOptions; +expectType<{maxRecursionDepth: 10; bracketNotation: false; leavesOnly: true; depth: 2}>(someOptionsSpecified); + +declare const someOptionsSpecified2: ApplyDefaultOptions; +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; // Possible when `exactOptionalPropertyTypes` is disabled +expectType(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; +expectType(optionalOptionsGetOverwritten); + +declare const neverAsOptionsGetOverwritten: ApplyDefaultOptions; +expectType(neverAsOptionsGetOverwritten); + +declare const anyAsOptionsGetOverwritten: ApplyDefaultOptions; +expectType(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, {}>; + +// @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` should be assignable to `Required` +type SomeType = _SomeType>; +type _SomeType> = Options;