From d460b4b32f407b6fa5cf57a5a8171afb1d54dd6e Mon Sep 17 00:00:00 2001 From: Tyler Miller Date: Wed, 11 Aug 2021 06:06:35 -0700 Subject: [PATCH] Add `ExtendsOr`, `ExtendsAnd`, and `ExtendsThenElse` types --- index.d.ts | 5 ++ readme.md | 1 + source/extends-or-and-then-else.d.ts | 129 +++++++++++++++++++++++++++ test-d/extends-or-and-then-else.ts | 14 +++ 4 files changed, 149 insertions(+) create mode 100644 source/extends-or-and-then-else.d.ts create mode 100644 test-d/extends-or-and-then-else.ts diff --git a/index.d.ts b/index.d.ts index f7b4b3800..346e40004 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,6 +34,11 @@ export {SetReturnType} from './source/set-return-type'; export {Asyncify} from './source/asyncify'; export {Simplify} from './source/simplify'; export {Jsonify} from './source/jsonify'; +export { + ExtendsOr, + ExtendsAnd, + ExtendsThenElse, +} from './source/extends-or-and-then-else'; // Template literal types export {CamelCase} from './source/camel-case'; diff --git a/readme.md b/readme.md index cfa6c037b..4d9cd801b 100644 --- a/readme.md +++ b/readme.md @@ -124,6 +124,7 @@ Click the type names for complete docs. - [`Includes`](source/includes.ts) - Returns a boolean for whether the given array includes the given item. - [`Simplify`](source/simplify.d.ts) - Flatten the type output to improve type hints shown in editors. - [`Jsonify`](source/jsonify.d.ts) - Transform a type to one that is assignable to the `JsonValue` type. +- [`ExtendsOr, ExtendsAnd, ExtendsThenElse`](source/extends-or-and-then-else.d.ts) - `A extends B ? A : C`, `A extends B ? C : A,` `A extends B ? C : D` respectively. A small collection of short-circuit and other conditional shorthands (e.g. || and && type utilities). ### Template literal types diff --git a/source/extends-or-and-then-else.d.ts b/source/extends-or-and-then-else.d.ts new file mode 100644 index 000000000..61d5a6f4f --- /dev/null +++ b/source/extends-or-and-then-else.d.ts @@ -0,0 +1,129 @@ +/** +Can be used as a short-circuit-esque/fallback-esque operator. +Probably most useful when the underlying type of the 1st passed +parameter is unknown or a union of sorts (such as in Object[key] +or Array[key] situations), but can be used anywhere a shorthand +for `A extends B ? A : C` seems useful. + +@example +``` +import {ExtendsOr} from 'type-fest'; + +// Say we are trying to convert an async api that uses callbacks +// as the last param to use native promises instead, resolving the +// last param the callback is called with (e.g. npm request package). +// We don't want Functions that don't take a Function as their last +// param to be promise-wrapped, nor do we want to alter the types of +// non-Function properties. + +type PromiseApi = { + [key in keyof Api]: LastTupleElement< + Parameters< + ExtendsOr< + Api[key], // <--unsure if Function, + // outer Parameters<> would err without ExtendsOr<> here + Function, // <--if Function, passthru type + (a: 0) => void // <--else fallback to this + > + > + > extends Function + ? ( + ...args: RemoveLastParam + ) => Promise< + LastTupleElement< + Parameters>> + > + > + : Api[key]; // passthru everything else untouched, including: + // non-Function types, Functions with no params, + // and Functions whose last param isn't Function +}; +``` + +@category Utilities +*/ +export type ExtendsOr = SomeType extends Test + ? SomeType + : OrType; + +/** +The inverse of ExtendsOr<>. + +Probably most useful when the underlying type of the 1st passed +parameter is unknown or a union of sorts (such as in Object[key] +or Array[key] situations), but can be used anywhere a shorthand +for `A extends B ? C : A` seems useful. @see ExtendsOr + +@example +``` +import {ExtendsAnd} from 'type-fest'; + +type TruthyObject = { + [key in keyof Obj]: ExtendsAnd; +}; + +// TruthyObject<{ notBool: string; bool1: boolean; bool2: boolean }> + +// becomes... + +// { +// notBool: string; +// bool1: true; +// bool2: true; +// } +``` + +@category Utilities +*/ +export type ExtendsAnd = SomeType extends Test + ? AndType + : SomeType; + +/** +Conditional shorthand. + +Can be used anywhere a conditional expression could, +but otherwise cannot be used to "infer" types. + +@example +``` +import {ExtendsThenElse, ExtendsAnd} from 'type-fest'; + +type TruthyObjectWithDates = { + [key in keyof Obj]: ExtendsThenElse< + Obj[key], + boolean, + true, + ExtendsAnd< + Obj[key], + string, + `${number}${number}${number}${number}-${number}${number}-${number}${number}` + > + >; +}; + +// TruthyObjectWithDates<{ +// date1: string; +// bool1: boolean; +// bool2: boolean; +// leftAlone: Function +// }> + +// becomes... + +// { +// date1: `${number}${number}${number}${number}-${number}${number}-${number}${number}`; +// bool1: true; +// bool2: true; +// leftAlone: Function +// } +``` + +@category Utilities +*/ +export type ExtendsThenElse< + SomeType, + Test, + ThenValue, + ElseValue, +> = SomeType extends Test ? ThenValue : ElseValue; diff --git a/test-d/extends-or-and-then-else.ts b/test-d/extends-or-and-then-else.ts new file mode 100644 index 000000000..52487517a --- /dev/null +++ b/test-d/extends-or-and-then-else.ts @@ -0,0 +1,14 @@ +import {expectType} from 'tsd'; +import {ExtendsOr, ExtendsAnd, ExtendsThenElse} from '../index'; + +declare const bool: boolean; +declare const string: string; + +expectType>(bool); // Passthru match +expectType>(string); // Fallback/short-circuit non-match + +expectType>(string); // Coerce match +expectType>(bool); // Passthru non-match + +expectType>(string); // Then +expectType>(string); // Else