|
| 1 | +import type {Primitive} from './primitive'; |
| 2 | +import type {Numeric} from './numeric'; |
| 3 | +import type {IsNever, IsNotFalse} from './internal'; |
| 4 | + |
| 5 | +/** |
| 6 | +Returns a boolean for whether the given type `T` is the specified `LiteralType`. |
| 7 | +
|
| 8 | +@link https://stackoverflow.com/a/52806744/10292952 |
| 9 | +
|
| 10 | +@example |
| 11 | +``` |
| 12 | +LiteralCheck<1, number> |
| 13 | +//=> true |
| 14 | +
|
| 15 | +LiteralCheck<number, number> |
| 16 | +//=> false |
| 17 | +
|
| 18 | +LiteralCheck<1, string> |
| 19 | +//=> false |
| 20 | +``` |
| 21 | +*/ |
| 22 | +type LiteralCheck<T, LiteralType extends Primitive> = ( |
| 23 | + IsNever<T> extends false // Must be wider than `never` |
| 24 | + ? [T] extends [LiteralType] // Must be narrower than `LiteralType` |
| 25 | + ? [LiteralType] extends [T] // Cannot be wider than `LiteralType` |
| 26 | + ? false |
| 27 | + : true |
| 28 | + : false |
| 29 | + : false |
| 30 | +); |
| 31 | + |
| 32 | +/** |
| 33 | +Returns a boolean for whether the given type `T` is one of the specified literal types in `LiteralUnionType`. |
| 34 | +
|
| 35 | +@example |
| 36 | +``` |
| 37 | +LiteralChecks<1, Numeric> |
| 38 | +//=> true |
| 39 | +
|
| 40 | +LiteralChecks<1n, Numeric> |
| 41 | +//=> true |
| 42 | +
|
| 43 | +LiteralChecks<bigint, Numeric> |
| 44 | +//=> false |
| 45 | +``` |
| 46 | +*/ |
| 47 | +type LiteralChecks<T, LiteralUnionType> = ( |
| 48 | + // Conditional type to force union distribution. |
| 49 | + // If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck<T, LiteralType>` will evaluate to `false` for the whole union. |
| 50 | + // If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`) |
| 51 | + IsNotFalse<LiteralUnionType extends Primitive |
| 52 | + ? LiteralCheck<T, LiteralUnionType> |
| 53 | + : never |
| 54 | + > |
| 55 | +); |
| 56 | + |
| 57 | +/** |
| 58 | +Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). |
| 59 | +
|
| 60 | +Useful for: |
| 61 | + - providing strongly-typed string manipulation functions |
| 62 | + - constraining strings to be a string literal |
| 63 | + - type utilities, such as when constructing parsers and ASTs |
| 64 | +
|
| 65 | +@example |
| 66 | +``` |
| 67 | +import type {IsStringLiteral} from 'type-fest'; |
| 68 | +
|
| 69 | +type CapitalizedString<T extends string> = IsStringLiteral<T> extends true ? Capitalize<T> : string; |
| 70 | +
|
| 71 | +// https://github.com/yankeeinlondon/native-dash/blob/master/src/capitalize.ts |
| 72 | +function capitalize<T extends Readonly<string>>(input: T): CapitalizedString<T> { |
| 73 | + return (input.slice(0, 1).toUpperCase() + input.slice(1)) as CapitalizedString<T>; |
| 74 | +} |
| 75 | +
|
| 76 | +const output = capitalize('hello, world!'); |
| 77 | +//=> 'Hello, world!' |
| 78 | +``` |
| 79 | +
|
| 80 | +@category Utilities |
| 81 | +@category Type Guard |
| 82 | +*/ |
| 83 | +export type IsStringLiteral<T> = LiteralCheck<T, string>; |
| 84 | + |
| 85 | +/** |
| 86 | +Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). |
| 87 | +
|
| 88 | +Useful for: |
| 89 | + - providing strongly-typed functions when given literal arguments |
| 90 | + - type utilities, such as when constructing parsers and ASTs |
| 91 | +
|
| 92 | +@example |
| 93 | +``` |
| 94 | +import type {IsNumericLiteral} from 'type-fest'; |
| 95 | +
|
| 96 | +// https://github.com/inocan-group/inferred-types/blob/master/src/types/boolean-logic/EndsWith.ts |
| 97 | +type EndsWith<TValue, TEndsWith extends string> = |
| 98 | + TValue extends string |
| 99 | + ? IsStringLiteral<TEndsWith> extends true |
| 100 | + ? IsStringLiteral<TValue> extends true |
| 101 | + ? TValue extends `${string}${TEndsWith}` |
| 102 | + ? true |
| 103 | + : false |
| 104 | + : boolean |
| 105 | + : boolean |
| 106 | + : TValue extends number |
| 107 | + ? IsNumericLiteral<TValue> extends true |
| 108 | + ? EndsWith<`${TValue}`, TEndsWith> |
| 109 | + : false |
| 110 | + : false; |
| 111 | +
|
| 112 | +function endsWith<Input extends string | number, End extends string>(input: Input, end: End) { |
| 113 | + return `${input}`.endsWith(end) as EndsWith<Input, End>; |
| 114 | +} |
| 115 | +
|
| 116 | +endsWith('abc', 'c'); |
| 117 | +//=> true |
| 118 | +
|
| 119 | +endsWith(123456, '456'); |
| 120 | +//=> true |
| 121 | +
|
| 122 | +const end = '123' as string; |
| 123 | +
|
| 124 | +endsWith('abc123', end); |
| 125 | +//=> boolean |
| 126 | +``` |
| 127 | +
|
| 128 | +@category Utilities |
| 129 | +@category Type Guard |
| 130 | +*/ |
| 131 | +export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>; |
| 132 | + |
| 133 | +/** |
| 134 | +Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). |
| 135 | +
|
| 136 | +Useful for: |
| 137 | + - providing strongly-typed functions when given literal arguments |
| 138 | + - type utilities, such as when constructing parsers and ASTs |
| 139 | +
|
| 140 | +@example |
| 141 | +``` |
| 142 | +import type {IsBooleanLiteral} from 'type-fest'; |
| 143 | +
|
| 144 | +const id = 123; |
| 145 | +
|
| 146 | +type GetId<AsString extends boolean> = |
| 147 | + IsBooleanLiteral<AsString> extends true |
| 148 | + ? AsString extends true |
| 149 | + ? `${typeof id}` |
| 150 | + : typeof id |
| 151 | + : number | string; |
| 152 | +
|
| 153 | +function getId<AsString extends boolean = false>(options?: {asString: AsString}) { |
| 154 | + return (options?.asString ? `${id}` : id) as GetId<AsString>; |
| 155 | +} |
| 156 | +
|
| 157 | +const numberId = getId(); |
| 158 | +//=> 123 |
| 159 | +
|
| 160 | +const stringId = getId({asString: true}); |
| 161 | +//=> '123' |
| 162 | +
|
| 163 | +declare const runtimeBoolean: boolean; |
| 164 | +const eitherId = getId({asString: runtimeBoolean}); |
| 165 | +//=> number | string |
| 166 | +``` |
| 167 | +
|
| 168 | +@category Utilities |
| 169 | +@category Type Guard |
| 170 | +*/ |
| 171 | +export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>; |
| 172 | + |
| 173 | +/** |
| 174 | +Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). |
| 175 | +
|
| 176 | +Useful for: |
| 177 | + - providing strongly-typed functions when given literal arguments |
| 178 | + - type utilities, such as when constructing parsers and ASTs |
| 179 | +
|
| 180 | +@example |
| 181 | +``` |
| 182 | +import type {IsSymbolLiteral} from 'type-fest'; |
| 183 | +
|
| 184 | +type Get<Obj extends Record<symbol, number>, Key extends keyof Obj> = |
| 185 | + IsSymbolLiteral<Key> extends true |
| 186 | + ? Obj[Key] |
| 187 | + : number; |
| 188 | +
|
| 189 | +function get<Obj extends Record<symbol, number>, Key extends keyof Obj>(o: Obj, key: Key) { |
| 190 | + return o[key] as Get<Obj, Key>; |
| 191 | +} |
| 192 | +
|
| 193 | +const symbolLiteral = Symbol('literal'); |
| 194 | +const symbolValue: symbol = Symbol('value'); |
| 195 | +
|
| 196 | +get({[symbolLiteral]: 1} as const, symbolLiteral); |
| 197 | +//=> 1 |
| 198 | +
|
| 199 | +get({[symbolValue]: 1} as const, symbolValue); |
| 200 | +//=> number |
| 201 | +``` |
| 202 | +
|
| 203 | +@category Utilities |
| 204 | +@category Type Guard |
| 205 | +*/ |
| 206 | +export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>; |
| 207 | + |
| 208 | +/** Helper type for `IsLiteral`. */ |
| 209 | +type IsLiteralUnion<T> = |
| 210 | + | IsStringLiteral<T> |
| 211 | + | IsNumericLiteral<T> |
| 212 | + | IsBooleanLiteral<T> |
| 213 | + | IsSymbolLiteral<T>; |
| 214 | + |
| 215 | +/** |
| 216 | +Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). |
| 217 | +
|
| 218 | +Useful for: |
| 219 | + - providing strongly-typed functions when given literal arguments |
| 220 | + - type utilities, such as when constructing parsers and ASTs |
| 221 | +
|
| 222 | +@example |
| 223 | +``` |
| 224 | +import type {IsLiteral} from 'type-fest'; |
| 225 | +
|
| 226 | +// https://github.com/inocan-group/inferred-types/blob/master/src/types/string-literals/StripLeading.ts |
| 227 | +export type StripLeading<A, B> = |
| 228 | + A extends string |
| 229 | + ? B extends string |
| 230 | + ? IsLiteral<A> extends true |
| 231 | + ? string extends B ? never : A extends `${B & string}${infer After}` ? After : A |
| 232 | + : string |
| 233 | + : A |
| 234 | + : A; |
| 235 | +
|
| 236 | +function stripLeading<Input extends string, Strip extends string>(input: Input, strip: Strip) { |
| 237 | + return input.replace(`^${strip}`, '') as StripLeading<Input, Strip>; |
| 238 | +} |
| 239 | +
|
| 240 | +stripLeading('abc123', 'abc'); |
| 241 | +//=> '123' |
| 242 | +
|
| 243 | +const str = 'abc123' as string; |
| 244 | +
|
| 245 | +stripLeading(str, 'abc'); |
| 246 | +//=> string |
| 247 | +``` |
| 248 | +
|
| 249 | +@category Utilities |
| 250 | +@category Type Guard |
| 251 | +*/ |
| 252 | +export type IsLiteral<T extends Primitive> = IsNotFalse<IsLiteralUnion<T>>; |
0 commit comments