Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add IsX/IfX types for any/never/unknown #564

Merged
merged 30 commits into from Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
344a1b2
feat: add `IsX`/`IfX` types for `any`/`never`/ `unknown`
tommy-mitchell Mar 6, 2023
e146dfd
fix: `IfAny` test case
tommy-mitchell Mar 6, 2023
3a5878e
Update is-if-any.ts
sindresorhus Mar 6, 2023
be4f89f
Update is-if-never.ts
sindresorhus Mar 6, 2023
0a0fa77
Update is-if-unknown.ts
sindresorhus Mar 6, 2023
254b7a1
chore: update `tsd`, uncomment test cases with `ts2707` error
tommy-mitchell Mar 6, 2023
546af2a
chore: separate into `is-x` and `if-x` files
tommy-mitchell Mar 6, 2023
1431554
fix: no hard wrapping
tommy-mitchell Mar 6, 2023
1036255
feat(`any`): add documentation
tommy-mitchell Mar 7, 2023
6a39a29
feat(`never`): add documentation
tommy-mitchell Mar 8, 2023
009f06d
fix(`any`): unify documentation
tommy-mitchell Mar 8, 2023
e192e47
feat: move `IsNull` to `internal`
tommy-mitchell Mar 8, 2023
11fb302
fix(`never`): add source of example
tommy-mitchell Mar 8, 2023
43f34f6
chore: add tests for `void`
tommy-mitchell Mar 8, 2023
1530ef9
feat(`unknown`): add documentation
tommy-mitchell Mar 8, 2023
b391d44
Update package.json
sindresorhus Mar 10, 2023
d0ec7b3
chore: merge changes from #563
tommy-mitchell Mar 10, 2023
d4e10d1
chore(`is-literal`): update `IsNever` import
tommy-mitchell Mar 10, 2023
eee302c
chore: set primary category as `Type Guard`
tommy-mitchell Mar 14, 2023
853cfbf
docs: reorganize readme and add note for `IsT`/`IfT` types
tommy-mitchell Mar 14, 2023
4e692d8
chore: merge upstream changes
tommy-mitchell Mar 14, 2023
dc5ba9c
docs(`IfAny`): update description and example
tommy-mitchell Mar 16, 2023
28bbfae
chore(`IfAny`): consistent variable name
tommy-mitchell Mar 16, 2023
81d38ae
chore: consistent test ordering
tommy-mitchell Mar 16, 2023
42e4b26
Merge branch 'main' into feat-expose-internals
sindresorhus Mar 17, 2023
d55804c
chore: merge changes from main
tommy-mitchell Mar 27, 2023
af4196a
docs: rename `alternate` to `conditional`
tommy-mitchell Mar 27, 2023
f61b07d
chore(`IfT`): improve descriptions and links
tommy-mitchell Mar 27, 2023
64e5639
docs(`IfT`): simplify examples
tommy-mitchell Mar 27, 2023
a15b774
Merge branch 'main' into feat-expose-internals
tommy-mitchell Mar 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions index.d.ts
Expand Up @@ -84,6 +84,12 @@ export type {
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';
export type {IfNever} from './source/if-never';
export type {IsUnknown} from './source/is-unknown';
export type {IfUnknown} from './source/if-unknown';

// Template literal types
export type {CamelCase} from './source/camel-case';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -36,7 +36,7 @@
"devDependencies": {
"@sindresorhus/tsconfig": "~0.7.0",
"expect-type": "^0.15.0",
"tsd": "^0.24.1",
"tsd": "^0.27.0",
"typescript": "^4.9.3",
"xo": "^0.53.1"
},
Expand Down
33 changes: 32 additions & 1 deletion readme.md
Expand Up @@ -173,12 +173,43 @@ 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.
- [`TaggedUnion`](source/tagged-union.d.ts) - Create a union of types that share a common discriminant property.

### Type Guard

#### `IsType` vs. `IfType`

For every `IsT` type (e.g. `IsAny`), there is an associated `IfT` type that can help simplify conditional types. While the `IsT` types return a `boolean`, the `IfT` types act like an `If`/`Else` - they resolve to the given `TypeIfT` or `TypeIfNotT` depending on whether `IsX` is `true` or not. By default, `IfT` returns a `boolean`:

```ts
type IfAny<T, TypeIfAny = true, TypeIfNotAny = false> = (
IsAny<T> extends true ? TypeIfAny : TypeIfNotAny
);
```

#### Usage

```ts
import type {IsAny, IfAny} from 'type-fest';

type ShouldBeTrue = IsAny<any> extends true ? true : false;
//=> true

type ShouldBeFalse = IfAny<'not any'>;
//=> false

type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
//=> 'never'
```

- [`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).
- [`TaggedUnion`](source/tagged-union.d.ts) - Create a union of types that share a common discriminant property.
- [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. (Alternate: `IfAny`.)
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved
- [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. (Alternate: `IfNever`.)
- [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. (Alternate: `IfUnknown`.)

### JSON

Expand Down
29 changes: 29 additions & 0 deletions source/if-any.d.ts
@@ -0,0 +1,29 @@
import type {IsAny} from './is-any';

/**
An `If`/`Else`-like type that resolves depending on whether the given type is `any`.
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved

If the given type `T` is `any`, the returned type is `TypeIfAny`. Otherwise, the return type is `TypeIfNotAny`. If only `T` is specified, `TypeIfAny` will be `true` and `TypeIfNotAny` will be false.
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved

@see IsAny
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check if this is linkified in VS Code? We need a syntax that like to the type correctly so users can just click through.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hovers correctly in source files, but the rendered hover text for IfAny does not link to IsAny.

Using @see {@link IsAny} does linkify in the hover text, but it seems to go to the import statement in if-any.d.ts.

Related, most type-fest types with an @see don't use the @link syntax, so they're not correct either.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having @see makes it clearer.

With:

image

Without:

image


@example
```
import type {IsAny, IfAny} from 'type-fest';

type ShouldBeTrue = IsAny<any> extends true ? true : false;
//=> true

type ShouldBeFalse = IfAny<'not any'>;
//=> false

type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
//=> 'never'
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved
```
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved
tommy-mitchell marked this conversation as resolved.
Show resolved Hide resolved

@category Type Guard
@category Utilities
*/
export type IfAny<T, TypeIfAny = true, TypeIfNotAny = false> = (
IsAny<T> extends true ? TypeIfAny : TypeIfNotAny
);
53 changes: 53 additions & 0 deletions source/if-never.d.ts
@@ -0,0 +1,53 @@
import type {IsNever} from './is-never';

/**
If the given type `T` is `never`, the returned type is `TypeIfNever`. Otherwise, the return type is `TypeIfNotNever`. If only `T` is specified, `TypeIfNever` will be `true` and `TypeIfNotNever` will be false.

@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
@link https://stackoverflow.com/a/53984913/10292952
@link https://www.zhenghao.io/posts/ts-never

Useful in type utilities, such as checking if something does not occur.

@example
```
import type {IfNever} from 'type-fest';

type And<A, B> =
A extends true
? B extends true
? true
: false
: false;

// https://github.com/andnp/SimplyTyped/blob/master/src/types/strings.ts
type AreStringsEqual<A extends string, B extends string> =
And<
IfNever<Exclude<A, B>>,
IfNever<Exclude<B, A>>
>;

type EndIfEqual<I extends string, O extends string> =
AreStringsEqual<I, O> extends true
? never
: void;

function endIfEqual<I extends string, O extends string>(input: I, output: O): EndIfEqual<I, O> {
if (input === output) {
process.exit(0);
}
}

endIfEqual('abc', 'abc');
//=> never

endIfEqual('abc', '123');
//=> void
```

@category Type Guard
@category Utilities
*/
export type IfNever<T, TypeIfNever = true, TypeIfNotNever = false> = (
IsNever<T> extends true ? TypeIfNever : TypeIfNotNever
);
48 changes: 48 additions & 0 deletions source/if-unknown.d.ts
@@ -0,0 +1,48 @@
import type {IsUnknown} from './is-unknown';

/**
If the given type `T` is `unknown`, the returned type is `TypeIfUnknown`. Otherwise, the return type is `TypeIfNotUnknown`. If only `T` is specified, `TypeIfUnknown` will be `true` and `TypeIfNotUnknown` will be false.

Useful in type utilities, such as when dealing with unknown data from API calls.

@example
```
import type {IfUnknown} from 'type-fest';

// https://github.com/pajecawav/tiny-global-store/blob/master/src/index.ts
type Action<TState, TPayload = void> = IfUnknown<
TPayload,
(state: TState) => TState,
(state: TState, payload: TPayload) => TState
>;

class Store<TState> {
constructor(private state: TState) {}

execute<TPayload = void>(action: Action<TState, TPayload>, payload?: TPayload): TState {
this.state = action(this.state, payload);
return this.state;
}

// ... other methods
}

const store = new Store({value: 1});
declare const someExternalData: unknown;

store.execute(state => ({value: state.value + 1}));
//=> `TPayload` is `void`

store.execute((state, payload) => ({value: state.value + payload}), 5);
//=> `TPayload` is `5`

store.execute((state, payload) => ({value: state.value + payload}), someExternalData);
//=> Errors: `action` is `(state: TState) => TState`
```

@category Type Guard
@category Utilities
*/
export type IfUnknown<T, TypeIfUnknown = true, TypeIfNotUnknown = false> = (
IsUnknown<T> extends true ? TypeIfUnknown : TypeIfNotUnknown
);
23 changes: 6 additions & 17 deletions source/internal.d.ts
@@ -1,6 +1,7 @@
import type {Primitive} from './primitive';
import type {Simplify} from './simplify';
import type {Trim} from './trim';
import type {IsAny} from './is-any';

/**
Infer the length of the given array `<T>`.
Expand Down Expand Up @@ -158,23 +159,6 @@ export type IsNumeric<T extends string> = T extends `${number}`
: false
: false;

/**
Returns a boolean for whether the the type is `any`.

@link https://stackoverflow.com/a/49928360/1490091
*/
export type IsAny<T> = 0 extends 1 & T ? true : false;

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

/**
Returns a boolean for whether the the type is `unknown`.
*/
export type IsUnknown<T> = IsNever<T> extends false ? T extends unknown ? unknown extends T ? IsAny<T> extends false ? true : false : false : false : false;

/**
For an object T, if it has any properties that are a union with `undefined`, make those into optional properties instead.

Expand Down Expand Up @@ -262,3 +246,8 @@ export type HasMultipleCallSignatures<T extends (...arguments: any[]) => unknown
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`.
*/
export type IsNull<T> = [T] extends [null] ? true : false;
29 changes: 29 additions & 0 deletions source/is-any.d.ts
@@ -0,0 +1,29 @@
/**
Returns a boolean for whether the given type is `any`.

@link https://stackoverflow.com/a/49928360/1490091

Useful in type utilities, such as disallowing `any`s to be passed to a function.

@example
```
import type {IsAny} from 'type-fest';

const typedObject = {a: 1, b: 2} as const;
const anyObject: any = {a: 1, b: 2};

function get<O extends (IsAny<O> extends true ? {} : Record<string, number>), K extends keyof O = keyof O>(obj: O, key: K) {
return obj[key];
}

const typedA = get(typedObject, 'a');
//=> 1

const anyA = get(anyObject, 'a');
//=> any
```

@category Type Guard
@category Utilities
*/
export type IsAny<T> = 0 extends 1 & T ? true : false;
1 change: 1 addition & 0 deletions source/is-equal.d.ts
Expand Up @@ -21,6 +21,7 @@ type Includes<Value extends readonly any[], Item> =
: false;
```

@category Type Guard
@category Utilities
*/
export type IsEqual<A, B> =
Expand Down
13 changes: 7 additions & 6 deletions source/is-literal.d.ts
@@ -1,6 +1,7 @@
import type {Primitive} from './primitive';
import type {Numeric} from './numeric';
import type {IsNever, IsNotFalse} from './internal';
import type {IsNotFalse} from './internal';
import type {IsNever} from './is-never';

/**
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
Expand Down Expand Up @@ -77,8 +78,8 @@ const output = capitalize('hello, world!');
//=> 'Hello, world!'
```

@category Utilities
@category Type Guard
@category Utilities
*/
export type IsStringLiteral<T> = LiteralCheck<T, string>;

Expand Down Expand Up @@ -125,8 +126,8 @@ endsWith('abc123', end);
//=> boolean
```

@category Utilities
@category Type Guard
@category Utilities
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;

Expand Down Expand Up @@ -165,8 +166,8 @@ const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```

@category Utilities
@category Type Guard
@category Utilities
*/
export type IsBooleanLiteral<T> = LiteralCheck<T, boolean>;

Expand Down Expand Up @@ -200,8 +201,8 @@ get({[symbolValue]: 1} as const, symbolValue);
//=> number
```

@category Utilities
@category Type Guard
@category Utilities
*/
export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;

Expand Down Expand Up @@ -246,7 +247,7 @@ stripLeading(str, 'abc');
//=> string
```

@category Utilities
@category Type Guard
@category Utilities
*/
export type IsLiteral<T extends Primitive> = IsNotFalse<IsLiteralUnion<T>>;
49 changes: 49 additions & 0 deletions source/is-never.d.ts
@@ -0,0 +1,49 @@
/**
Returns a boolean for whether the given type is `never`.

@link https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
@link https://stackoverflow.com/a/53984913/10292952
@link https://www.zhenghao.io/posts/ts-never

Useful in type utilities, such as checking if something does not occur.

@example
```
import type {IsNever} from 'type-fest';

type And<A, B> =
A extends true
? B extends true
? true
: false
: false;

// https://github.com/andnp/SimplyTyped/blob/master/src/types/strings.ts
type AreStringsEqual<A extends string, B extends string> =
And<
IsNever<Exclude<A, B>> extends true ? true : false,
IsNever<Exclude<B, A>> extends true ? true : false
>;

type EndIfEqual<I extends string, O extends string> =
AreStringsEqual<I, O> extends true
? never
: void;

function endIfEqual<I extends string, O extends string>(input: I, output: O): EndIfEqual<I, O> {
if (input === output) {
process.exit(0);
}
}

endIfEqual('abc', 'abc');
//=> never

endIfEqual('abc', '123');
//=> void
```

@category Type Guard
@category Utilities
*/
export type IsNever<T> = [T] extends [never] ? true : false;