Skip to content

Commit

Permalink
chore: merge changes from sindresorhus#563
Browse files Browse the repository at this point in the history
  • Loading branch information
tommy-mitchell committed Mar 10, 2023
2 parents b391d44 + 9d00bac commit d0ec7b3
Show file tree
Hide file tree
Showing 6 changed files with 347 additions and 1 deletion.
7 changes: 7 additions & 0 deletions index.d.ts
Expand Up @@ -76,6 +76,13 @@ export type {HasRequiredKeys} from './source/has-required-keys';
export type {Spread} from './source/spread';
export type {TupleToUnion} from './source/tuple-to-union';
export type {IsEqual} from './source/is-equal';
export type {
IsLiteral,
IsStringLiteral,
IsNumericLiteral,
IsBooleanLiteral,
IsSymbolLiteral,
} from './source/is-literal';
export type {IsAny} from './source/is-any';
export type {IfAny} from './source/if-any';
export type {IsNever} from './source/is-never';
Expand Down
5 changes: 5 additions & 0 deletions readme.md
Expand Up @@ -171,6 +171,11 @@ Click the type names for complete docs.
- [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields.
- [`Spread`](source/spread.d.ts) - Mimic the type inferred by TypeScript when merging two objects or two arrays/tuples using the spread syntax.
- [`IsEqual`](source/is-equal.d.ts) - Returns a boolean for whether the two given types are equal.
- [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsStringLiteral`](source/is-literal.d.ts) - 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).
- [`IsNumericLiteral`](source/is-literal.d.ts) - 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).
- [`IsBooleanLiteral`](source/is-literal.d.ts) - 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).
- [`IsSymbolLiteral`](source/is-literal.d.ts) - 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).
- [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`.
- [`IfAny`](source/if-any.d.ts) - An `If`/`Else` like type that resolves whether the given type is `any`.
- [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`.
Expand Down
5 changes: 5 additions & 0 deletions source/internal.d.ts
Expand Up @@ -242,6 +242,11 @@ export type HasMultipleCallSignatures<T extends (...arguments: any[]) => unknown
: true
: false;

/**
Returns a boolean for whether the given `boolean` is not `false`.
*/
export type IsNotFalse<T extends boolean> = [T] extends [false] ? false : true;

/**
Returns a boolean for whether the given type is `null`.
*/
Expand Down
252 changes: 252 additions & 0 deletions source/is-literal.d.ts
@@ -0,0 +1,252 @@
import type {Primitive} from './primitive';
import type {Numeric} from './numeric';
import type {IsNever, IsNotFalse} from './internal';

/**
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
@link https://stackoverflow.com/a/52806744/10292952
@example
```
LiteralCheck<1, number>
//=> true
LiteralCheck<number, number>
//=> false
LiteralCheck<1, string>
//=> false
```
*/
type LiteralCheck<T, LiteralType extends Primitive> = (
IsNever<T> extends false // Must be wider than `never`
? [T] extends [LiteralType] // Must be narrower than `LiteralType`
? [LiteralType] extends [T] // Cannot be wider than `LiteralType`
? false
: true
: false
: false
);

/**
Returns a boolean for whether the given type `T` is one of the specified literal types in `LiteralUnionType`.
@example
```
LiteralChecks<1, Numeric>
//=> true
LiteralChecks<1n, Numeric>
//=> true
LiteralChecks<bigint, Numeric>
//=> false
```
*/
type LiteralChecks<T, LiteralUnionType> = (
// Conditional type to force union distribution.
// If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck<T, LiteralType>` will evaluate to `false` for the whole union.
// If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`)
IsNotFalse<LiteralUnionType extends Primitive
? LiteralCheck<T, LiteralUnionType>
: never
>
);

/**
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).
Useful for:
- providing strongly-typed string manipulation functions
- constraining strings to be a string literal
- type utilities, such as when constructing parsers and ASTs
@example
```
import type {IsStringLiteral} from 'type-fest';
type CapitalizedString<T extends string> = IsStringLiteral<T> extends true ? Capitalize<T> : string;
// https://github.com/yankeeinlondon/native-dash/blob/master/src/capitalize.ts
function capitalize<T extends Readonly<string>>(input: T): CapitalizedString<T> {
return (input.slice(0, 1).toUpperCase() + input.slice(1)) as CapitalizedString<T>;
}
const output = capitalize('hello, world!');
//=> 'Hello, world!'
```
@category Utilities
@category Type Guard
*/
export type IsStringLiteral<T> = LiteralCheck<T, string>;

/**
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).
Useful for:
- providing strongly-typed functions when given literal arguments
- type utilities, such as when constructing parsers and ASTs
@example
```
import type {IsNumericLiteral} from 'type-fest';
// https://github.com/inocan-group/inferred-types/blob/master/src/types/boolean-logic/EndsWith.ts
type EndsWith<TValue, TEndsWith extends string> =
TValue extends string
? IsStringLiteral<TEndsWith> extends true
? IsStringLiteral<TValue> extends true
? TValue extends `${string}${TEndsWith}`
? true
: false
: boolean
: boolean
: TValue extends number
? IsNumericLiteral<TValue> extends true
? EndsWith<`${TValue}`, TEndsWith>
: false
: false;
function endsWith<Input extends string | number, End extends string>(input: Input, end: End) {
return `${input}`.endsWith(end) as EndsWith<Input, End>;
}
endsWith('abc', 'c');
//=> true
endsWith(123456, '456');
//=> true
const end = '123' as string;
endsWith('abc123', end);
//=> boolean
```
@category Utilities
@category Type Guard
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;

/**
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).
Useful for:
- providing strongly-typed functions when given literal arguments
- type utilities, such as when constructing parsers and ASTs
@example
```
import type {IsBooleanLiteral} from 'type-fest';
const id = 123;
type GetId<AsString extends boolean> =
IsBooleanLiteral<AsString> extends true
? AsString extends true
? `${typeof id}`
: typeof id
: number | string;
function getId<AsString extends boolean = false>(options?: {asString: AsString}) {
return (options?.asString ? `${id}` : id) as GetId<AsString>;
}
const numberId = getId();
//=> 123
const stringId = getId({asString: true});
//=> '123'
declare const runtimeBoolean: boolean;
const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```
@category Utilities
@category Type Guard
*/
export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>;

/**
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).
Useful for:
- providing strongly-typed functions when given literal arguments
- type utilities, such as when constructing parsers and ASTs
@example
```
import type {IsSymbolLiteral} from 'type-fest';
type Get<Obj extends Record<symbol, number>, Key extends keyof Obj> =
IsSymbolLiteral<Key> extends true
? Obj[Key]
: number;
function get<Obj extends Record<symbol, number>, Key extends keyof Obj>(o: Obj, key: Key) {
return o[key] as Get<Obj, Key>;
}
const symbolLiteral = Symbol('literal');
const symbolValue: symbol = Symbol('value');
get({[symbolLiteral]: 1} as const, symbolLiteral);
//=> 1
get({[symbolValue]: 1} as const, symbolValue);
//=> number
```
@category Utilities
@category Type Guard
*/
export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;

/** Helper type for `IsLiteral`. */
type IsLiteralUnion<T> =
| IsStringLiteral<T>
| IsNumericLiteral<T>
| IsBooleanLiteral<T>
| IsSymbolLiteral<T>;

/**
Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
Useful for:
- providing strongly-typed functions when given literal arguments
- type utilities, such as when constructing parsers and ASTs
@example
```
import type {IsLiteral} from 'type-fest';
// https://github.com/inocan-group/inferred-types/blob/master/src/types/string-literals/StripLeading.ts
export type StripLeading<A, B> =
A extends string
? B extends string
? IsLiteral<A> extends true
? string extends B ? never : A extends `${B & string}${infer After}` ? After : A
: string
: A
: A;
function stripLeading<Input extends string, Strip extends string>(input: Input, strip: Strip) {
return input.replace(`^${strip}`, '') as StripLeading<Input, Strip>;
}
stripLeading('abc123', 'abc');
//=> '123'
const str = 'abc123' as string;
stripLeading(str, 'abc');
//=> string
```
@category Utilities
@category Type Guard
*/
export type IsLiteral<T extends Primitive> = IsNotFalse<IsLiteralUnion<T>>;
15 changes: 14 additions & 1 deletion test-d/internal.ts
@@ -1,5 +1,10 @@
import {expectType} from 'tsd';
import type {IsWhitespace, IsNumeric, IsNull} from '../source/internal';
import type {
IsWhitespace,
IsNumeric,
IsNotFalse,
IsNull,
} from '../source/internal';

expectType<IsWhitespace<''>>(false);
expectType<IsWhitespace<' '>>(true);
Expand Down Expand Up @@ -28,6 +33,14 @@ expectType<IsNumeric<'1 2'>>(false);
expectType<IsNumeric<'1_200'>>(false);
expectType<IsNumeric<' 1 '>>(false);

expectType<IsNotFalse<true>>(true);
expectType<IsNotFalse<boolean>>(true);
expectType<IsNotFalse<true | false>>(true);
expectType<IsNotFalse<true | false | false | false>>(true);
expectType<IsNotFalse<false>>(false);
expectType<IsNotFalse<false | false>>(false);
expectType<IsNotFalse<false | false | false | false>>(false);

// https://www.typescriptlang.org/docs/handbook/type-compatibility.html
expectType<IsNull<null>>(true);
expectType<IsNull<any>>(true);
Expand Down
64 changes: 64 additions & 0 deletions test-d/is-literal.ts
@@ -0,0 +1,64 @@
import {expectError, expectType} from 'tsd';
import type {
IsLiteral,
IsStringLiteral,
IsNumericLiteral,
IsBooleanLiteral,
IsSymbolLiteral,
} from '../index';

const stringLiteral = '';
const numberLiteral = 1;
// @ts-expect-error: suppress BigInt literal tsd warning
const bigintLiteral = 1n;
const booleanLiteral = true;
const symbolLiteral = Symbol('');

declare const _string: string;
declare const _number: number;
declare const _bigint: bigint;
declare const _boolean: boolean;
declare const _symbol: symbol;

// Literals should be true
expectType<IsLiteral<typeof stringLiteral>>(true);
expectType<IsLiteral<typeof numberLiteral>>(true);
expectType<IsLiteral<typeof bigintLiteral>>(true);
expectType<IsLiteral<typeof booleanLiteral>>(true);
expectType<IsLiteral<typeof symbolLiteral>>(true);

// Primitives should be false
expectType<IsLiteral<typeof _string>>(false);
expectType<IsLiteral<typeof _number>>(false);
expectType<IsLiteral<typeof _bigint>>(false);
expectType<IsLiteral<typeof _boolean>>(false);
expectType<IsLiteral<typeof _symbol>>(false);

// Null, undefined, and non-primitives should fail all literal checks
expectType<IsLiteral<null>>(false);
expectType<IsLiteral<undefined>>(false);
expectType<IsLiteral<any>>(false);
expectType<IsLiteral<never>>(false);

expectType<IsStringLiteral<typeof stringLiteral>>(true);
expectType<IsStringLiteral<typeof _string>>(false);

expectType<IsNumericLiteral<typeof numberLiteral>>(true);
expectType<IsNumericLiteral<typeof bigintLiteral>>(true);
expectType<IsNumericLiteral<typeof _number>>(false);
expectType<IsNumericLiteral<typeof _bigint>>(false);

expectType<IsBooleanLiteral<typeof booleanLiteral>>(true);
expectType<IsBooleanLiteral<typeof _boolean>>(false);

expectType<IsSymbolLiteral<typeof symbolLiteral>>(true);
expectType<IsSymbolLiteral<typeof _symbol>>(false);

declare const anything: any;

// Missing generic parameter
expectError<IsLiteral>(anything);
expectError<IsStringLiteral>(anything);
expectError<IsNumericLiteral>(anything);
expectError<IsBooleanLiteral>(anything);
expectError<IsSymbolLiteral>(anything);

0 comments on commit d0ec7b3

Please sign in to comment.