This chapter explains functionality that is used in the code examples to explain results and errors. We have to consider two levels:
The functions and generic types that help us, have to be imported: The import statements to do so are shown in this chapter, but omitted elsewhere in this book.
assert.*
Expected results are checked via the following assertion functions from the Node.js module node:assert
:
assert.equal()
tests equality via ===
assert.deepEqual()
tests equality by deeply comparing nested objects (incl. Arrays).
assert.throws()
complains if the callback parameter does not throw an exception.
This is an example of using these assertions:
import assert from 'node:assert/strict';
assert.equal(
3 + ' apples',
'3 apples'
);
assert.deepEqual(
[...['a', 'b'], ...['c', 'd']],
['a', 'b', 'c', 'd']
);
assert.throws(
() => Object.freeze({}).prop = true,
/^TypeError: Cannot add property prop, object is not extensible/
);
In the first line, the specifier of the imported module has the suffix /strict
. That enables strict assertion mode, which uses ===
and not ==
for comparisons.
assertType<T>(v)
Function assertType()
is provided by the TypeScript library asserttt
.
The function call assertType<T>(v)
asserts that the (dynamic) value v
has the (static) type T
:
import { assertType } from 'asserttt';
let value = 123;
assertType<number>(value);
Assert<B>
asserttt
also provides the utility type Assert<B>
, which asserts that the type B
(usually an instantiated generic type) is true
:
import { type Assert, type Equal, type Not } from 'asserttt';
type Pair<X> = [X, X];
type _ = [
Assert<Equal<
Pair<'a'>, ['a', 'a']
>>,
Assert<Not<Equal<
Pair<'a'>, ['x', 'x']
>>>,
];
asserttt
has several predicates (generic types that construct booleans) that we can use with Assert<>
. In the previous example, we have used:
Equal<T1, T2>
Not<B>
@ts-expect-error
In this book, @ts-expect-error
is used to show TypeScript compiler errors:
// @ts-expect-error: The value 'null' cannot be used here.
const value = null.myProp;
How does TypeScript handle such a directive?
If there is an error in a line after a @ts-expect-error
comment then that error is ignored and compilation succeeds.
If there is no error then TypeScript complains:
Unused '@ts-expect-error' directive.
In other words: TypeScript checks that there is an error but not what error it is. All text after @ts-expect-error
is ignored (including the colon).
To get more thorough checks, I use the tool ts-expect-error
which checks if the suppressed error messages match the texts after @ts-expect-error:
.
When it comes to displaying type information for TypeScript code, there are some very pretty approaches out there – e.g. Shiki Twoslash which uses the twoslash syntax.
This book uses in-code checks (as described above) even though that doesn’t look as nice. Why?
This notation makes you think about types in terms of tests. That prepares you for computed types and for coding exercises – whose notation is similar.
The notation makes it possible to test the code examples automatically, via the Markcheck tool for Markdown. That ensures that they don’t contain errors. Twoslash only specifies which types to display; it does not check that those types are as expected.
For printed books, HTML still isn’t where I’d like it to be. Thus, I can’t use Shiki Twoslash there.
Minor downside of Shiki Twoslash: You need to run the TypeScript type checker in order to render a book. With my notation, I only need to run it when I check the code examples.