tsdjs / tsd

Check TypeScript type definitions
MIT License
2.4k stars 66 forks source link

How can I assert that a value is not a callable? #96

Closed pastelmind closed 3 years ago

pastelmind commented 3 years ago

As of writing, expectError() does not support checking if a value is uncallable:

const Foo = 1;
expectError(Foo());       // ts(2349) This expression is not callable.
                          // Type 'Number' has no call signatures.

class MyClass {}
expectError(MyClass());   // ts(2348) Value of type 'typeof MyClass' is not callable.
                          // Did you mean to include 'new'?

When I run tsd, it complains with "Expected an error, but found none." for both examples above.

Some questions:

  1. Can expectError() be extended to add support for this? Does it need a new assertion function?
  2. Is this within the scope of tsd? Or should I just use // @ts-expect-error instead?

Edit: I did some experiments.

class Foo {}
const bar = 1;
function baz(): never { throw new Error(); }

// expectType<never>() is not good enough.
// It cannot distinguish uncallable values from functions that never return at all
// You also need @ts-expect-error anyway to stop TypeScript from complaining,
// but this defeats the purpose of using tsd in the first place.

// @ts-expect-error
expectType<never>(Foo());
// @ts-expect-error
expectType<never>(bar());
expectType<never>(baz());

// expectNotAssignable<Function>() isn't good enough because it doesn't catch
// attempting to call ES6 classes like plain functions.
// But this seems to work!

expectNotAssignable<CallableFunction>(Foo);
expectNotAssignable<CallableFunction>(bar);
expectAssignable<CallableFunction>(baz);

TL;DR: Use expectNotAssignable<CallableFunction>().

It might be nice to add this use case to the docs.