UNPKG

8.15 kBTypeScriptView Raw
1import type {Primitive} from './primitive';
2import type {Numeric} from './numeric';
3import type {IsNotFalse, IsPrimitive} from './internal';
4import type {IsNever} from './is-never';
5import type {IfNever} from './if-never';
6
7/**
8Returns 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```
14LiteralCheck<1, number>
15//=> true
16
17LiteralCheck<number, number>
18//=> false
19
20LiteralCheck<1, string>
21//=> false
22```
23*/
24type 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/**
37Returns a boolean for whether the given type `T` is one of the specified literal types in `LiteralUnionType`.
38
39@example
40```
41LiteralChecks<1, Numeric>
42//=> true
43
44LiteralChecks<1n, Numeric>
45//=> true
46
47LiteralChecks<bigint, Numeric>
48//=> false
49```
50*/
51type 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/**
62Returns 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
64Useful 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
69The implementation of this type is inspired by the trick mentioned in this [StackOverflow answer](https://stackoverflow.com/a/68261113/420747).
70
71@example
72```
73import type {IsStringLiteral} from 'type-fest';
74
75type CapitalizedString<T extends string> = IsStringLiteral<T> extends true ? Capitalize<T> : string;
76
77// https://github.com/yankeeinlondon/native-dash/blob/master/src/capitalize.ts
78function capitalize<T extends Readonly<string>>(input: T): CapitalizedString<T> {
79 return (input.slice(0, 1).toUpperCase() + input.slice(1)) as CapitalizedString<T>;
80}
81
82const output = capitalize('hello, world!');
83//=> 'Hello, world!'
84```
85
86@example
87```
88// String types with infinite set of possible values return `false`.
89
90import type {IsStringLiteral} from 'type-fest';
91
92type AllUppercaseStrings = IsStringLiteral<Uppercase<string>>;
93//=> false
94
95type 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
100type 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
107type L1 = Length<Lowercase<string>>;
108//=> number
109
110type L2 = Length<`${number}`>;
111//=> number
112```
113
114@category Type Guard
115@category Utilities
116*/
117export 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`.
120T extends string
121 ? {} extends Record<T, never>
122 ? false
123 : true
124 : false>;
125
126/**
127Returns 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
129Useful for:
130 - providing strongly-typed functions when given literal arguments
131 - type utilities, such as when constructing parsers and ASTs
132
133@example
134```
135import type {IsNumericLiteral} from 'type-fest';
136
137// https://github.com/inocan-group/inferred-types/blob/master/src/types/boolean-logic/EndsWith.ts
138type 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
153function endsWith<Input extends string | number, End extends string>(input: Input, end: End) {
154 return `${input}`.endsWith(end) as EndsWith<Input, End>;
155}
156
157endsWith('abc', 'c');
158//=> true
159
160endsWith(123456, '456');
161//=> true
162
163const end = '123' as string;
164
165endsWith('abc123', end);
166//=> boolean
167```
168
169@category Type Guard
170@category Utilities
171*/
172export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;
173
174/**
175Returns 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
177Useful for:
178 - providing strongly-typed functions when given literal arguments
179 - type utilities, such as when constructing parsers and ASTs
180
181@example
182```
183import type {IsBooleanLiteral} from 'type-fest';
184
185const id = 123;
186
187type GetId<AsString extends boolean> =
188 IsBooleanLiteral<AsString> extends true
189 ? AsString extends true
190 ? `${typeof id}`
191 : typeof id
192 : number | string;
193
194function getId<AsString extends boolean = false>(options?: {asString: AsString}) {
195 return (options?.asString ? `${id}` : id) as GetId<AsString>;
196}
197
198const numberId = getId();
199//=> 123
200
201const stringId = getId({asString: true});
202//=> '123'
203
204declare const runtimeBoolean: boolean;
205const eitherId = getId({asString: runtimeBoolean});
206//=> number | string
207```
208
209@category Type Guard
210@category Utilities
211*/
212export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>;
213
214/**
215Returns 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
217Useful for:
218 - providing strongly-typed functions when given literal arguments
219 - type utilities, such as when constructing parsers and ASTs
220
221@example
222```
223import type {IsSymbolLiteral} from 'type-fest';
224
225type Get<Obj extends Record<symbol, number>, Key extends keyof Obj> =
226 IsSymbolLiteral<Key> extends true
227 ? Obj[Key]
228 : number;
229
230function get<Obj extends Record<symbol, number>, Key extends keyof Obj>(o: Obj, key: Key) {
231 return o[key] as Get<Obj, Key>;
232}
233
234const symbolLiteral = Symbol('literal');
235const symbolValue: symbol = Symbol('value');
236
237get({[symbolLiteral]: 1} as const, symbolLiteral);
238//=> 1
239
240get({[symbolValue]: 1} as const, symbolValue);
241//=> number
242```
243
244@category Type Guard
245@category Utilities
246*/
247export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;
248
249/** Helper type for `IsLiteral`. */
250type IsLiteralUnion<T> =
251 | IsStringLiteral<T>
252 | IsNumericLiteral<T>
253 | IsBooleanLiteral<T>
254 | IsSymbolLiteral<T>;
255
256/**
257Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
258
259Useful for:
260 - providing strongly-typed functions when given literal arguments
261 - type utilities, such as when constructing parsers and ASTs
262
263@example
264```
265import type {IsLiteral} from 'type-fest';
266
267// https://github.com/inocan-group/inferred-types/blob/master/src/types/string-literals/StripLeading.ts
268export 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
277function stripLeading<Input extends string, Strip extends string>(input: Input, strip: Strip) {
278 return input.replace(`^${strip}`, '') as StripLeading<Input, Strip>;
279}
280
281stripLeading('abc123', 'abc');
282//=> '123'
283
284const str = 'abc123' as string;
285
286stripLeading(str, 'abc');
287//=> string
288```
289
290@category Type Guard
291@category Utilities
292*/
293export type IsLiteral<T> =
294 IsPrimitive<T> extends true
295 ? IsNotFalse<IsLiteralUnion<T>>
296 : false;