Skip to content

Commit

Permalink
fix: correctly narrow return type of isObject, simplify isArray (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone authored Sep 7, 2022
1 parent 97e27b1 commit 4b10530
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 30 deletions.
45 changes: 24 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,41 @@ Function documentation available [here](https://alcalzone.github.io/shared-utils
<!--
Placeholder for the next version (at the beginning of the line):
### __WORK IN PROGRESS__
-->
-->
### __WORK IN PROGRESS__
* Fix: correctly narrow return type of `isObject`

### 4.0.5 (2022-09-07)
* Fix: correctly narrow return type of isArray for `{}` arguments

* Fix: correctly narrow return type of isArray for `{}` arguments

### 4.0.4 (2022-09-07)
* Fix: narrowed type for `isArray` is inferred correctly for `readonly` arrays

* Fix: narrowed type for `isArray` is inferred correctly for `readonly` arrays

### 4.0.3 (2022-08-27)
* Update `isArray` to be compatible with TS 4.8 changes

* Update `isArray` to be compatible with TS 4.8 changes

### 4.0.1 (2021-11-15)
`SortedQueue`: Fixed an issue where inserting an item before the first one would cause the queue to lose track of items

`SortedQueue`: Fixed an issue where inserting an item before the first one would cause the queue to lose track of items

### 4.0.0 (2021-06-19)
* Node.js 12+ is now required

* Node.js 12+ is now required

### 3.0.4 (2021-04-24)
* Fix compatibility of `wait()` with Electron if `unref` is `true`
* Dependency updates

* Dependency updates

### 3.0.3 (2021-03-09)
#### Fixes
* Fixed compatibility with TypeScript 4.2

* Fixed compatibility with TypeScript 4.2

### 3.0.2 (2021-01-16)
#### Fixes
* The argument to `resolve` of `DeferredPromise` is no longer optional, except for `Promise<void>`

* The argument to `resolve` of `DeferredPromise` is no longer optional, except for `Promise<void>`

### 3.0.1 (2020-12-05)
#### Fixes
* The typeguard `isObject` no longer narrows the type of the argument to `object`

* The typeguard `isObject` no longer narrows the type of the argument to `object`

### 3.0.0 (2020-08-16)
#### Breaking changes
* Renamed the following types:
Expand All @@ -69,7 +72,7 @@ Function documentation available [here](https://alcalzone.github.io/shared-utils

#### Fixes
* The type `CallbackAPIReturnType` now works with `strictNullChecks`.
* The type `Promisify` is no longer experimental and no longer messes up the inferred signature argument names

* The type `Promisify` is no longer experimental and no longer messes up the inferred signature argument names

### 2.3.0 (2020-06-08)
* Added optional `unref` parameter to `async` -> `wait(ms, [unref])`
14 changes: 8 additions & 6 deletions src/typeguards/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
type ExtractObject<T> =
T extends readonly unknown[] ? never
: {} extends T ? Record<string | number | symbol, unknown>
: T extends Record<string | number | symbol, unknown> ? T
: never;

/**
* Tests whether the given variable is a real object and not an Array
* @param it The variable to test
*/
export function isObject<T>(it: T): it is T & Record<string, unknown> {
export function isObject<T>(it: T): it is T & ExtractObject<T> {
// This is necessary because:
// typeof null === 'object'
// typeof [] === 'object'
// [] instanceof Object === true
return Object.prototype.toString.call(it) === "[object Object]";
}

type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IsAny<T> = IfAny<T, true, never>;

type ExtractArray<T> =
true extends IsAny<T> ? unknown[]
: T extends readonly unknown[] ? T
T extends readonly unknown[] ? T
: {} extends T ? (T & unknown[])
: never;

Expand Down
77 changes: 74 additions & 3 deletions src/typeguards/typeguards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import type { Equals } from "../types";
function assertTrue<T extends true>() {
return undefined!;
}
function assertFalse<T extends false>() {
return undefined!;
}
type UnspecifiedObject = Record<string | number | symbol, unknown>;

describe("lib/typeguards =>", () => {
describe("isObject() => ", () => {
Expand Down Expand Up @@ -37,6 +35,79 @@ describe("lib/typeguards =>", () => {
const foo = { a: { b: "c" } } as unknown;
isObject(foo) && isObject(foo.a) && isObject(foo.a.b);
});

it("inferred types are correct", () => {
const _any = undefined as any;
const _unknown = undefined as unknown;
const _unknownArray = _unknown as unknown[];
const _number = _unknown as number;
const _numberArray = _unknown as number[];
const _readonlyNumberArray = _unknown as readonly number[];
const _nonNullish = _unknown as {};
const _specificObjectNullable = _unknown as
| { a: number }
| undefined;
const _objectOrArray = _unknown as { a: number } | string[];

if (isObject(_any)) {
assertTrue<Equals<typeof _any, any>>();
_any;
// ^?
}

if (isObject(_unknown)) {
assertTrue<Equals<typeof _unknown, UnspecifiedObject>>();
_unknown;
// ^?
}

if (isObject(_unknownArray)) {
assertTrue<Equals<typeof _unknownArray, never>>();
_unknownArray;
// ^?
}

if (isObject(_number)) {
assertTrue<Equals<typeof _number, never>>();
_number;
// ^?
}

if (isObject(_numberArray)) {
assertTrue<Equals<typeof _numberArray, never>>();
_numberArray;
// ^?
}

if (isObject(_readonlyNumberArray)) {
assertTrue<Equals<typeof _readonlyNumberArray, never>>();
_readonlyNumberArray;
// ^?
}

if (isObject(_nonNullish)) {
assertTrue<Equals<typeof _nonNullish, UnspecifiedObject>>();
_nonNullish;
// ^?
}

if (isObject(_specificObjectNullable)) {
assertTrue<
Equals<
typeof _specificObjectNullable,
NonNullable<typeof _specificObjectNullable>
>
>();
_specificObjectNullable;
// ^?
}

if (isObject(_objectOrArray)) {
assertTrue<Equals<typeof _objectOrArray, { a: number }>>();
_objectOrArray;
// ^?
}
});
});

describe("isArray() => ", () => {
Expand Down

0 comments on commit 4b10530

Please sign in to comment.