Skip to content

Commit

Permalink
Improve compatibility of hasProperty (MetaMask#80)
Browse files Browse the repository at this point in the history
* Improve compatibility of `hasProperty`

The `hasProperty` function is now compatible with a wider variety of
objects. The `Record<PropertyKey, unknown>` constraint used previously
was exluding `Error`s, and any object-like things expressed as a custom
class or interface. Now all of those are accepted.

* Ignore `tsd` tests when building

These test modules are not meant to be compiled directly with
TypeScript. They sometimes include errors by-design that would blow up
if building with TypeScript.

* Re-enable TypeScript in `test-d.ts` files to enable linting

The type error assertion was replaced with an alternative that does not
introduce a compile-time error.
  • Loading branch information
Gudahtt authored Feb 14, 2023
1 parent 8d13a40 commit 5b6e31b
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 9 deletions.
42 changes: 35 additions & 7 deletions src/misc.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expectAssignable, expectNotAssignable } from 'tsd';
import { expectAssignable, expectNotAssignable, expectType } from 'tsd';

import { isObject, hasProperty, RuntimeObject } from './misc';

Expand Down Expand Up @@ -61,6 +61,34 @@ if (hasProperty(overlappingTypesExample, 'foo')) {
expectAssignable<Record<'baz', unknown>>(overlappingTypesExample);
}

const exampleErrorWithCode = new Error('test');
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
exampleErrorWithCode.code = 999;

// Establish that trying to check for a custom property on an error results in failure
expectNotAssignable<{ code: any }>(exampleErrorWithCode);

// Using custom Error property is allowed after checking with `hasProperty`
if (hasProperty(exampleErrorWithCode, 'code')) {
expectType<unknown>(exampleErrorWithCode.code);
}

// `hasProperty` is compatible with interfaces
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface HasPropertyInterfaceExample {
a: number;
}
const hasPropertyInterfaceExample: HasPropertyInterfaceExample = { a: 0 };
hasProperty(hasPropertyInterfaceExample, 'a');

// `hasProperty` is compatible with classes
class HasPropertyClassExample {
a!: number;
}
const hasPropertyClassExample = new HasPropertyClassExample();
hasProperty(hasPropertyClassExample, 'a');

//=============================================================================
// RuntimeObject
//=============================================================================
Expand Down Expand Up @@ -101,14 +129,14 @@ expectNotAssignable<RuntimeObject>(Symbol('test'));
// The RuntimeObject type gets confused by interfaces. This interface is a valid object,
// but it's incompatible with the RuntimeObject type.
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface A {
interface RuntimeObjectInterfaceExample {
a: number;
}
const a: A = { a: 0 };
expectNotAssignable<RuntimeObject>(a);
const runtimeObjectInterfaceExample: RuntimeObjectInterfaceExample = { a: 0 };
expectNotAssignable<RuntimeObject>(runtimeObjectInterfaceExample);

class Foo {
class RuntimeObjectClassExample {
a!: number;
}
const foo = new Foo();
expectNotAssignable<RuntimeObject>(foo);
const runtimeObjectClassExample = new RuntimeObjectClassExample();
expectNotAssignable<RuntimeObject>(runtimeObjectClassExample);
3 changes: 2 additions & 1 deletion src/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export function isObject(value: unknown): value is RuntimeObject {
* name, regardless of whether it is enumerable or not.
*/
export const hasProperty = <
ObjectToCheck extends RuntimeObject,
// eslint-disable-next-line @typescript-eslint/ban-types
ObjectToCheck extends Object,
Property extends PropertyKey,
>(
objectToCheck: ObjectToCheck,
Expand Down
6 changes: 5 additions & 1 deletion tsconfig.build.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@
"sourceMap": true
},
"include": ["./src/**/*.ts"],
"exclude": ["./src/**/*.test.ts", "./src/__fixtures__"]
"exclude": [
"./src/**/*.test.ts",
"./src/**/*.test-d.ts",
"./src/__fixtures__"
]
}

0 comments on commit 5b6e31b

Please sign in to comment.