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