diff --git a/src/misc.test-d.ts b/src/misc.test-d.ts index b5531105b..f6f7a9458 100644 --- a/src/misc.test-d.ts +++ b/src/misc.test-d.ts @@ -1,6 +1,6 @@ import { expectAssignable, expectNotAssignable } from 'tsd'; -import { isObject, hasProperty, RuntimeObject } from '.'; +import { isObject, hasProperty, RuntimeObject } from './misc'; //============================================================================= // isObject @@ -36,6 +36,20 @@ if (isObject(unknownObject)) { expectNotAssignable>(unknownObject); } +// An object is accepted after `hasProperty` is used to prove that it has the required property. +if (isObject(unknownObject) && hasProperty(unknownObject, 'foo')) { + expectAssignable>(unknownObject); +} + +// An object is accepted after `hasProperty` is used to prove that it has all required properties. +if ( + isObject(unknownObject) && + hasProperty(unknownObject, 'foo') && + hasProperty(unknownObject, 'bar') +) { + expectAssignable>(unknownObject); +} + // An object is not accepted after `hasProperty` has only been used to establish that some required properties exist. if (isObject(unknownObject) && hasProperty(unknownObject, 'foo')) { expectNotAssignable>(unknownObject); diff --git a/src/misc.test.ts b/src/misc.test.ts index 633dd5435..e6ce17066 100644 --- a/src/misc.test.ts +++ b/src/misc.test.ts @@ -109,10 +109,7 @@ describe('miscellaneous', () => { [ [{}, 'a'], [{ a: 1 }, 'b'], - // Object.hasOwnProperty does not work for arrays - // [['foo'], 0], - // [['foo'], '0'], - ] as any[] + ] as const ).forEach(([objectValue, property]) => { expect(hasProperty(objectValue, property)).toBe(false); }); diff --git a/src/misc.ts b/src/misc.ts index 132545553..e8cbf1042 100644 --- a/src/misc.ts +++ b/src/misc.ts @@ -32,10 +32,9 @@ export type PartialOrAbsent = Partial | null | undefined; export type NonEmptyArray = [Element, ...Element[]]; /** - * A JavaScript object that is not `null`, a function, or an array. The object - * can still be an instance of a class. + * A JavaScript object that is not `null`, a function, or an array. */ -export type RuntimeObject = Record; +export type RuntimeObject = Record; // // Type Guards @@ -80,17 +79,21 @@ export function isObject(value: unknown): value is RuntimeObject { // /** - * An alias for {@link Object.hasOwnProperty}. + * A type guard for ensuring an object has a property. * - * @param object - The object to check. + * @param objectToCheck - The object to check. * @param name - The property name to check for. * @returns Whether the specified object has an own property with the specified * name, regardless of whether it is enumerable or not. */ -export const hasProperty = ( - object: RuntimeObject, - name: string | number | symbol, -): boolean => Object.hasOwnProperty.call(object, name); +export const hasProperty = < + ObjectToCheck extends RuntimeObject, + Property extends PropertyKey, +>( + objectToCheck: ObjectToCheck, + name: Property, +): objectToCheck is ObjectToCheck & Record => + Object.hasOwnProperty.call(objectToCheck, name); export type PlainObject = Record;